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-registration-redux~=2.7',
|
||||
'django-rest-framework',
|
||||
'djangorestframework-camel-case',
|
||||
'pymemcache',
|
||||
'python-dotenv~=1.0.1',
|
||||
'ftfy~=6.2',
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<main id="integrations--page" class="main">
|
||||
<main id="integrations--page" class="main" data-render-sidebar=true>
|
||||
<div class="main__container">
|
||||
<section class="section">
|
||||
{% include "components/header/header.html" with title="Integrations" only %}
|
||||
|
||||
|
|
@ -41,5 +42,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
|
||||
{% block content %}
|
||||
<main id="login--page" class="main">
|
||||
<main id="login--page" class="main" data-render-sidebar=true>
|
||||
<div class="main__container">
|
||||
{% include "accounts/components/login-form.html" with form=form title="Login" confirm_text="Login" %}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<main id="password-change--page" class="main">
|
||||
{% url 'accounts:settings:home' as 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>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<main id="reddit--page" class="main">
|
||||
<main id="reddit--page" class="main" data-render-sidebar=true>
|
||||
<div class="main__container">
|
||||
<section class="section text-section">
|
||||
{% if error %}
|
||||
<h1 class="h1">{% trans "Reddit authorization failed" %}</h1>
|
||||
|
|
@ -16,5 +17,6 @@
|
|||
<a class="link" href="{% url 'accounts:settings:integrations' %}">{% trans "Return to integrations page" %}</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
|
||||
{% block content %}
|
||||
<main id="settings--page" class="main">
|
||||
<main id="settings--page" class="main" data-render-sidebar=true>
|
||||
<div class="main__container">
|
||||
{% include "accounts/components/settings-form.html" with form=form title="User profile" confirm_text="Save" %}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
from django.contrib.auth import views as django_views
|
||||
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"
|
||||
success_url = reverse_lazy("index")
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,13 @@ from newsreader.news.collection.reddit import (
|
|||
revoke_reddit_token,
|
||||
)
|
||||
from newsreader.news.collection.tasks import RedditTokenTask
|
||||
from newsreader.utils.views import NavListMixin
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IntegrationsView(TemplateView):
|
||||
class IntegrationsView(NavListMixin, TemplateView):
|
||||
template_name = "accounts/views/integrations.html"
|
||||
|
||||
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"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -1,32 +1,34 @@
|
|||
from django.contrib.auth import views as django_views
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from newsreader.utils.views import NavListMixin
|
||||
|
||||
|
||||
# PasswordResetView sends the mail
|
||||
# PasswordResetDoneView shows a success message for the above
|
||||
# PasswordResetConfirmView checks the link the user clicked and
|
||||
# prompts for a new password
|
||||
# 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"
|
||||
subject_template_name = "password-reset/password-reset-subject.txt"
|
||||
email_template_name = "password-reset/password-reset-email.html"
|
||||
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"
|
||||
|
||||
|
||||
class PasswordResetConfirmView(django_views.PasswordResetConfirmView):
|
||||
class PasswordResetConfirmView(NavListMixin, django_views.PasswordResetConfirmView):
|
||||
template_name = "password-reset/password-reset-confirm.html"
|
||||
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"
|
||||
|
||||
|
||||
class PasswordChangeView(django_views.PasswordChangeView):
|
||||
class PasswordChangeView(NavListMixin, django_views.PasswordChangeView):
|
||||
template_name = "accounts/views/password-change.html"
|
||||
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 newsreader.utils.views import NavListMixin
|
||||
|
||||
|
||||
# RegistrationView shows a registration form and sends the email
|
||||
# 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
|
||||
# ActivationResendView can be used when activation links are expired
|
||||
# RegistrationClosedView shows when registration is disabled
|
||||
class RegistrationView(registration_views.RegistrationView):
|
||||
class RegistrationView(NavListMixin, registration_views.RegistrationView):
|
||||
disallowed_url = reverse_lazy("accounts:register-closed")
|
||||
template_name = "registration/registration_form.html"
|
||||
success_url = reverse_lazy("accounts:register-complete")
|
||||
|
||||
|
||||
class RegistrationCompleteView(TemplateView):
|
||||
class RegistrationCompleteView(NavListMixin, TemplateView):
|
||||
template_name = "registration/registration_complete.html"
|
||||
|
||||
|
||||
class RegistrationClosedView(TemplateView):
|
||||
class RegistrationClosedView(NavListMixin, TemplateView):
|
||||
template_name = "registration/registration_closed.html"
|
||||
|
||||
|
||||
# Redirects or renders failed activation template
|
||||
class ActivationView(registration_views.ActivationView):
|
||||
class ActivationView(NavListMixin, registration_views.ActivationView):
|
||||
template_name = "registration/activation_failure.html"
|
||||
|
||||
def get_success_url(self, user):
|
||||
return ("accounts:activate-complete", (), {})
|
||||
|
||||
|
||||
class ActivationCompleteView(TemplateView):
|
||||
class ActivationCompleteView(NavListMixin, TemplateView):
|
||||
template_name = "registration/activation_complete.html"
|
||||
|
||||
|
||||
# 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"
|
||||
|
||||
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.models import User
|
||||
from newsreader.utils.views import NavListMixin
|
||||
|
||||
|
||||
class SettingsView(ModelFormMixin, FormView):
|
||||
class SettingsView(NavListMixin, ModelFormMixin, FormView):
|
||||
template_name = "accounts/views/settings.html"
|
||||
success_url = reverse_lazy("accounts:settings:home")
|
||||
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_RESET_ON_SUCCESS = True
|
||||
|
||||
# TODO: verify parser works correctly
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
|
|
@ -235,7 +236,13 @@ REST_FRAMEWORK = {
|
|||
"rest_framework.permissions.IsAuthenticated",
|
||||
"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 = {
|
||||
|
|
|
|||
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/categories/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 CategoryModal from './components/CategoryModal.js';
|
||||
import Messages from '../../components/Messages.js';
|
||||
import Sidebar from '../../components/Sidebar.js';
|
||||
|
||||
class App extends React.Component {
|
||||
selectCategory = ::this.selectCategory;
|
||||
|
|
@ -90,6 +91,9 @@ class App extends React.Component {
|
|||
return (
|
||||
<>
|
||||
{this.state.message && <Messages messages={[this.state.message]} />}
|
||||
<Sidebar navLinks={this.props.navLinks} />
|
||||
|
||||
<div className="main__container">
|
||||
<Card header={pageHeader} />
|
||||
{cards}
|
||||
{selectedCategory && (
|
||||
|
|
@ -99,6 +103,7 @@ class App extends React.Component {
|
|||
handleDelete={this.deleteCategory}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,15 @@ if (page) {
|
|||
let createUrl = document.getElementById('createUrl').textContent;
|
||||
let updateUrl = document.getElementById('updateUrl').textContent;
|
||||
|
||||
let linkScript = document.getElementById('Links');
|
||||
let navLinks = JSON.parse(linkScript.textContent);
|
||||
|
||||
ReactDOM.render(
|
||||
<App
|
||||
categories={categories}
|
||||
createUrl={createUrl.substring(1, createUrl.length - 2)}
|
||||
updateUrl={updateUrl.substring(1, updateUrl.length - 4)}
|
||||
navLinks={navLinks}
|
||||
/>,
|
||||
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 { fetchCategories } from './actions/categories';
|
||||
import { filterPosts } from './components/postlist/filters.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 PostModal from './components/PostModal.js';
|
||||
import Messages from '../../components/Messages.js';
|
||||
|
||||
class App extends React.Component {
|
||||
state = { postListNode: null }
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.postListRef = node => {
|
||||
this.setState({ postListNode: node });
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchCategories();
|
||||
}
|
||||
|
|
@ -19,11 +30,13 @@ class App extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<>
|
||||
<Sidebar />
|
||||
<HomepageSidebar navLinks={this.props.navLinks} />
|
||||
<PostList
|
||||
feedUrl={this.props.feedUrl}
|
||||
subredditUrl={this.props.subredditUrl}
|
||||
timezone={this.props.timezone}
|
||||
forwardedRef={this.postListRef}
|
||||
postsByType={this.props.postsByType}
|
||||
/>
|
||||
|
||||
{!isEqual(this.props.post, {}) && (
|
||||
|
|
@ -40,7 +53,7 @@ class App extends React.Component {
|
|||
/>
|
||||
)}
|
||||
|
||||
<ScrollTop />
|
||||
<ScrollTop postListNode={this.state.postListNode} />
|
||||
|
||||
{this.props.error && (
|
||||
<Messages messages={[{ type: 'error', text: this.props.error.message }]} />
|
||||
|
|
@ -52,6 +65,7 @@ class App extends React.Component {
|
|||
|
||||
const mapStateToProps = state => {
|
||||
const { error } = state.error;
|
||||
const postsByType = filterPosts(state)
|
||||
|
||||
if (!isEqual(state.selected.post, {})) {
|
||||
const ruleId = state.selected.post.rule.id;
|
||||
|
|
@ -65,10 +79,11 @@ const mapStateToProps = state => {
|
|||
rule,
|
||||
post: state.selected.post,
|
||||
selectedType: state.selected.item.type,
|
||||
postsByType: postsByType,
|
||||
};
|
||||
}
|
||||
|
||||
return { error, post: state.selected.post };
|
||||
return { error, post: state.selected.post, postsByType: postsByType, };
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class PostModal extends React.Component {
|
|||
const titleClassName = post.read ? 'post__title post__title--read' : 'post__title';
|
||||
const readButtonDisabled =
|
||||
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 = '';
|
||||
|
||||
|
|
@ -63,6 +63,7 @@ class PostModal extends React.Component {
|
|||
return (
|
||||
<div className="modal post-modal">
|
||||
<div className="post">
|
||||
<div className="post__container">
|
||||
<div className="post__header">
|
||||
<div className="post__actions">
|
||||
<button
|
||||
|
|
@ -83,11 +84,13 @@ class PostModal extends React.Component {
|
|||
</div>
|
||||
<div className="post__heading">
|
||||
<h2 className={titleClassName}>{`${post.title} `}</h2>
|
||||
<div className="post__meta-info">
|
||||
<span className="post__date">
|
||||
{publicationDate} {this.props.timezone}
|
||||
</span>
|
||||
<div className="post__meta">
|
||||
<div className="post__text">
|
||||
<span className="post__date">{publicationDate}</span>
|
||||
{post.author && <span className="post__author">{post.author}</span>}
|
||||
</div>
|
||||
|
||||
<div className="post__buttons">
|
||||
{this.props.category && (
|
||||
<span className="badge post__category" title={this.props.category.name}>
|
||||
<a
|
||||
|
|
@ -119,6 +122,8 @@ class PostModal extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HTML is sanitized by the collectors */}
|
||||
<div className="post__body" dangerouslySetInnerHTML={{ __html: post.body }} />
|
||||
|
|
|
|||
|
|
@ -3,35 +3,48 @@ import React from 'react';
|
|||
export default class ScrollTop extends React.Component {
|
||||
scrollListener = ::this.scrollListener;
|
||||
|
||||
state = { showTop: false, showBottom: false };
|
||||
state = {
|
||||
listenerAttached: false,
|
||||
showTop: false,
|
||||
showBottom: false
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('scroll', this.scrollListener);
|
||||
componentDidUpdate() {
|
||||
if (this.props.postListNode && !this.state.listenerAttached) {
|
||||
this.props.postListNode.addEventListener('scroll', this.scrollListener);
|
||||
|
||||
this.setState({ listenerAttached: true });
|
||||
}
|
||||
}
|
||||
|
||||
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({
|
||||
showTop: window.pageYOffset > 0 ? true : false,
|
||||
showBottom: showBottom,
|
||||
showTop: postList.scrollTop > window.innerHeight,
|
||||
showBottom: !elementEnd,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
const postList = this.props.postListNode;
|
||||
|
||||
return postList && (
|
||||
<div className="scroll-to-top">
|
||||
{this.state.showTop && (
|
||||
<i
|
||||
className="scroll-to-top__icon scroll-to-top__icon--top"
|
||||
onClick={() => window.scrollTo(0, 0)}
|
||||
onClick={() => postList.scroll({ top: 0 })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{this.state.showBottom && (
|
||||
<i
|
||||
className="scroll-to-top__icon scroll-to-top__icon--bottom"
|
||||
onClick={() => window.scrollTo(0, document.body.scrollHeight)}
|
||||
onClick={() => postList.scroll({ top: postList.scrollHeight })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class PostItem extends React.Component {
|
|||
|
||||
<div className="posts-info">
|
||||
<span className="posts-info__date" title={publicationDate}>
|
||||
{publicationDate} {this.props.timezone} {post.author && `By ${post.author}`}
|
||||
{publicationDate} {post.author && `By ${post.author}`}
|
||||
</span>
|
||||
{[CATEGORY_TYPE, SAVED_TYPE].includes(this.props.selected.type) && (
|
||||
<span className="badge">
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ class PostList extends React.Component {
|
|||
selected: this.props.selected,
|
||||
feedUrl: this.props.feedUrl,
|
||||
subredditUrl: this.props.subredditUrl,
|
||||
timezone: this.props.timezone,
|
||||
};
|
||||
|
||||
if (isLastItem?.id === item.id) {
|
||||
|
|
@ -96,7 +95,7 @@ class PostList extends React.Component {
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="posts">
|
||||
<div className="posts" ref={this.props.forwardedRef}>
|
||||
<ul className="posts__list">{postItems}</ul>
|
||||
{this.props.isFetching && <LoadingIndicator />}
|
||||
</div>
|
||||
|
|
@ -107,7 +106,6 @@ class PostList extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
isFetching: state.posts.isFetching,
|
||||
postsByType: filterPosts(state),
|
||||
next: state.selected.next,
|
||||
lastReached: state.selected.lastReached,
|
||||
selected: state.selected.item,
|
||||
|
|
@ -118,4 +116,4 @@ const mapDispatchToProps = dispatch => ({
|
|||
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 { connect } from 'react-redux';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { CATEGORY_TYPE } from '../../constants.js';
|
||||
import { selectCategory, fetchCategory } from '../../actions/categories.js';
|
||||
import { fetchPostsBySection } from '../../actions/posts.js';
|
||||
|
||||
import { isSelected } from './functions.js';
|
||||
import RuleItem from './RuleItem.js';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { RULE_TYPE } from '../../constants.js';
|
||||
import { selectRule, fetchRule } from '../../actions/rules.js';
|
||||
import { fetchPostsBySection } from '../../actions/posts.js';
|
||||
|
||||
import { isSelected } from './functions.js';
|
||||
|
||||
class RuleItem extends React.Component {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { filterCategories, filterRules } from './filters.js';
|
||||
|
||||
import { CATEGORY_TYPE, RULE_TYPE } from '../../constants.js';
|
||||
import Sidebar from "../../../../components/Sidebar.js";
|
||||
import LoadingIndicator from '../../../../components/LoadingIndicator.js';
|
||||
import { CATEGORY_TYPE, RULE_TYPE } from '../../constants.js';
|
||||
|
||||
import CategoryItem from './CategoryItem.js';
|
||||
import SavedItem from './SavedItem.js';
|
||||
import ReadButton from './ReadButton.js';
|
||||
|
||||
// TODO: show empty category message
|
||||
class Sidebar extends React.Component {
|
||||
import { filterCategories, filterRules } from './filters.js';
|
||||
|
||||
|
||||
class HomepageSidebar extends React.Component {
|
||||
render() {
|
||||
const categoryItems = this.props.categories.items.map(category => {
|
||||
const rules = this.props.rules.items.filter(rule => {
|
||||
const categoryItems = this.props.categories.items.map(category => { const rules = this.props.rules.items.filter(rule => {
|
||||
return rule.category === category.id;
|
||||
});
|
||||
|
||||
|
|
@ -32,22 +32,24 @@ class Sidebar extends React.Component {
|
|||
this.props.selected.item &&
|
||||
[CATEGORY_TYPE, RULE_TYPE].includes(this.props.selected.item.type);
|
||||
|
||||
|
||||
return (
|
||||
<div className="sidebar">
|
||||
<Sidebar navLinks={this.props.navLinks} includeBorder={true} >
|
||||
{(this.props.categories.isFetching || this.props.rules.isFetching) && (
|
||||
<LoadingIndicator />
|
||||
)}
|
||||
|
||||
<ul className="sidebar__nav">
|
||||
<ul className="sidebar__list">
|
||||
<SavedItem selected={this.props.selected.item} />
|
||||
{categoryItems}
|
||||
</ul>
|
||||
|
||||
{showReadButton && <ReadButton />}
|
||||
</div>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
categories: { ...state.categories, items: filterCategories(state.categories.items) },
|
||||
|
|
@ -55,4 +57,4 @@ const mapStateToProps = state => ({
|
|||
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 { feedUrl, subredditUrl, categoriesUrl } = settings;
|
||||
|
||||
const navLinks = JSON.parse(document.getElementById('Links').textContent);
|
||||
|
||||
const app = (
|
||||
<Provider store={store}>
|
||||
<App
|
||||
|
|
@ -22,6 +24,7 @@ if (page) {
|
|||
categoriesUrl={categoriesUrl.substring(1, categoriesUrl.length - 3)}
|
||||
timezone={settings.timezone}
|
||||
autoMarking={settings.autoMarking}
|
||||
navLinks={navLinks}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<main id="rule--page" class="main">
|
||||
{% url "news:collection:rules" as cancel_url %}
|
||||
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block content %}
|
||||
<main id="rule--page" class="main">
|
||||
{% if feed.error %}
|
||||
{% url "news:collection:rules" as cancel_url %}
|
||||
{% trans "Failed to retrieve posts" as title %}
|
||||
|
||||
<main id="rule--page" class="main" data-render-sidebar=true>
|
||||
<div class="main__container">
|
||||
{% if feed.error %}
|
||||
{% include "components/textbox/textbox.html" with title=title body=feed.error class="text-section--error" only %}
|
||||
{% endif %}
|
||||
|
||||
{% url "news:collection:rules" as cancel_url %}
|
||||
{% include "components/form/form.html" with form=form title="Update feed" cancel_url=cancel_url confirm_text="Save feed" %}
|
||||
<div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<main id="import--page" class="main">
|
||||
{% url "news:collection:rules" as cancel_url %}
|
||||
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
{% load i18n static filters %}
|
||||
|
||||
{% block content %}
|
||||
<main id="rules--page" class="main">
|
||||
<main id="rules--page" class="main" data-render-sidebar=true>
|
||||
<div class="main__container">
|
||||
<form class="form rules-form">
|
||||
{% csrf_token %}
|
||||
|
||||
|
|
@ -29,40 +30,68 @@
|
|||
<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" %}
|
||||
</th>
|
||||
<th class="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--url">{% trans "URL" %}</th>
|
||||
<th class="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--name">{% trans "Name" %}</th>
|
||||
<th class="table__heading rules-table__heading rules-table__heading--category">{% trans "Category" %}</th>
|
||||
<th class="table__heading rules-table__heading rules-table__heading--url">{% trans "URL" %}</th>
|
||||
<th class="table__heading rules-table__heading rules-table__heading--succeeded">{% trans "Successfuly ran" %}</th>
|
||||
<th class="table__heading rules-table__heading rules-table__heading--enabled">{% trans "Enabled" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table__body">
|
||||
<tbody class="table__body rules-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">
|
||||
<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>
|
||||
<td class="table__item rules-table__item" title="{{ rule.name }}">
|
||||
<a class="link" href="{{ rule.update_url }}">{{ rule.name }}</a>
|
||||
|
||||
<td
|
||||
class="table__item rules-table__item rules-table__item--name"
|
||||
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 }}">
|
||||
|
||||
<td
|
||||
class="table__item rules-table__item rules-table__item--category"
|
||||
title="{{ rule.category.name }}"
|
||||
>
|
||||
{% if rule.category %}
|
||||
<a class="link" href="{% url 'news:core:category-update' pk=rule.category.pk %}">{{ rule.category.name }}</a>
|
||||
<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
|
||||
class="table__item rules-table__item rules-table__item--url"
|
||||
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">
|
||||
|
||||
<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">
|
||||
|
||||
<td class="table__item rules-table__item rules-table__item--enabled">
|
||||
{% if rule.enabled %}
|
||||
<i class="fas fa-check"></i>
|
||||
{% else %}
|
||||
|
|
@ -81,7 +110,9 @@
|
|||
<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>
|
||||
<a class="link button" href="?page={{ page_obj.previous_page_number }}">
|
||||
{% trans "previous" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
|
||||
|
|
@ -93,11 +124,17 @@
|
|||
|
||||
<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>
|
||||
<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>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<main id="subreddit--page" class="main">
|
||||
{% url "news:collection:rules" as cancel_url %}
|
||||
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ from django_celery_beat.models import IntervalSchedule, PeriodicTask
|
|||
|
||||
from newsreader.news.collection.models import CollectionRule
|
||||
from newsreader.news.core.models import Category
|
||||
from newsreader.utils.views import NavListMixin
|
||||
|
||||
|
||||
class CollectionRuleViewMixin:
|
||||
class CollectionRuleViewMixin(NavListMixin):
|
||||
queryset = CollectionRule.objects.order_by("name")
|
||||
|
||||
def get_queryset(self):
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from newsreader.news.collection.views.base import (
|
|||
TaskCreationMixin,
|
||||
)
|
||||
from newsreader.utils.opml import parse_opml
|
||||
from newsreader.utils.views import NavListMixin
|
||||
|
||||
|
||||
class FeedUpdateView(CollectionRuleViewMixin, CollectionRuleDetailMixin, UpdateView):
|
||||
|
|
@ -36,7 +37,7 @@ class FeedCreateView(
|
|||
form_class = FeedForm
|
||||
|
||||
|
||||
class OPMLImportView(FormView):
|
||||
class OPMLImportView(NavListMixin, FormView):
|
||||
form_class = OPMLImportForm
|
||||
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.views.base import CollectionRuleViewMixin
|
||||
from newsreader.utils.views import NavListMixin
|
||||
|
||||
|
||||
class CollectionRuleListView(CollectionRuleViewMixin, ListView):
|
||||
class CollectionRuleListView(
|
||||
CollectionRuleViewMixin,
|
||||
NavListMixin,
|
||||
ListView
|
||||
):
|
||||
paginate_by = 50
|
||||
template_name = "news/collection/views/rules.html"
|
||||
context_object_name = "rules"
|
||||
|
||||
|
||||
class CollectionRuleBulkView(FormView):
|
||||
class CollectionRuleBulkView(NavListMixin, FormView):
|
||||
form_class = CollectionRuleBulkForm
|
||||
|
||||
def get_redirect_url(self):
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
|
||||
{{ categories_update_url|json_script:"updateUrl" }}
|
||||
{{ categories_create_url|json_script:"createUrl" }}
|
||||
{{ sidebar_links|json_script:"Links" }}
|
||||
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<main id="category--page" class="main">
|
||||
{% url "news:core:categories" as cancel_url %}
|
||||
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "sidebar.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<main id="category--page" class="main">
|
||||
{% url "news:core:categories" as cancel_url %}
|
||||
|
||||
<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>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
{% block scripts %}
|
||||
{{ homepageSettings|json_script:"homepageSettings" }}
|
||||
{{ sidebar_links|json_script:"Links" }}
|
||||
|
||||
{{ block.super }}
|
||||
{% endblock scripts %}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ from django.views.generic.list import ListView
|
|||
from newsreader.news.collection.models import CollectionRule
|
||||
from newsreader.news.core.forms import CategoryForm
|
||||
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"
|
||||
|
||||
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")
|
||||
|
||||
def get_queryset(self):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: Rubik, sans-serif;
|
||||
font-family: Inter;
|
||||
font-size: $font-size;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
@import '../../partials/variables';
|
||||
@import '../../lib/mixins';
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -9,6 +12,10 @@
|
|||
|
||||
background-color: var(--background-color);
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -16,17 +23,17 @@
|
|||
|
||||
padding: 15px 0;
|
||||
|
||||
border-bottom: 2px var(--lightest-accent-color) solid;
|
||||
border-bottom: 2px var(--border-color) solid;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
& .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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 0;
|
||||
|
||||
padding: 15px;
|
||||
border: none;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../partials/variables';
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -6,6 +8,10 @@
|
|||
|
||||
background-color: var(--background-color);
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__section {
|
||||
&--last {
|
||||
& .form__fieldset {
|
||||
|
|
@ -44,6 +50,7 @@
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@include block-padding;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,21 @@
|
|||
@import '../../partials/variables';
|
||||
|
||||
|
||||
.rules-form {
|
||||
@extend .form;
|
||||
|
||||
width: 90%;
|
||||
|
||||
@media (max-width: $wqhd-breakpoint) {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
|
||||
& .form__fieldset {
|
||||
gap: 15px;
|
||||
|
||||
& > * {
|
||||
margin: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,3 +27,6 @@
|
|||
@import './posts/index';
|
||||
@import './posts-info/index';
|
||||
@import './scroll-to-top/index';
|
||||
@import './menu/index';
|
||||
@import './nav-list/index';
|
||||
@import './checkbox-list/index';
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
&__controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@
|
|||
align-items: center;
|
||||
padding: 10px 0;
|
||||
|
||||
& > * {
|
||||
margin: 0 15px;
|
||||
}
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
position: absolute;
|
||||
left: 6px;
|
||||
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;
|
||||
|
||||
&:nth-child(1){
|
||||
|
|
|
|||
|
|
@ -1,7 +1,22 @@
|
|||
@import '../../partials/variables';
|
||||
|
||||
.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;
|
||||
|
||||
margin: 20px 0;
|
||||
@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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -6,7 +9,7 @@
|
|||
width: 100%;
|
||||
margin: 5px 0 20px 0;
|
||||
|
||||
color: $font-color;
|
||||
color: var(--font-color);
|
||||
|
||||
&__item {
|
||||
width: 80%;
|
||||
|
|
@ -17,6 +20,10 @@
|
|||
|
||||
background-color: $transparant-blue;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
&--error {
|
||||
background-color: $transparant-red;
|
||||
}
|
||||
|
|
@ -42,12 +49,10 @@
|
|||
}
|
||||
|
||||
&--fixed &__item {
|
||||
color: $white;
|
||||
background-color: $blue;
|
||||
}
|
||||
|
||||
&--fixed &__item--error {
|
||||
color: $white;
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +61,6 @@
|
|||
}
|
||||
|
||||
&--fixed &__item--success {
|
||||
color: $white;
|
||||
background-color: $green;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../partials/variables';
|
||||
|
||||
.modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -8,7 +10,7 @@
|
|||
height: 100%;
|
||||
top: 0;
|
||||
|
||||
background-color: $dark;
|
||||
background-color: var(--background-color);
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
|
|
@ -20,15 +22,17 @@
|
|||
|
||||
width: 60%;
|
||||
|
||||
background-color: var(--accent-color);
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
padding: 5px 20px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 10px 30px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
|
|
@ -36,6 +40,6 @@
|
|||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 10px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../partials/variables';
|
||||
|
||||
.post-modal {
|
||||
@extend .modal;
|
||||
|
||||
|
|
@ -6,5 +8,9 @@
|
|||
|
||||
cursor: pointer;
|
||||
|
||||
@media (min-width: $tablet-breakpoint) {
|
||||
background-color: var(--background-color-secondary);
|
||||
}
|
||||
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
@ -5,34 +8,43 @@
|
|||
|
||||
padding: 10px 0;
|
||||
width: 100%;
|
||||
height: map-deep-get($nav, height);
|
||||
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--background-color);
|
||||
|
||||
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;
|
||||
justify-content: flex-start;
|
||||
|
||||
width: 80%;
|
||||
list-style-type: none;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
margin: 0px 10px;
|
||||
& .nav-list {
|
||||
width: 80%;
|
||||
|
||||
& a {
|
||||
@extend .button;
|
||||
|
||||
font-size: 0.9em !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__item:last-child {
|
||||
margin: 0 10px 0 auto;
|
||||
|
||||
border-right: 2px solid var(--lighter-accent-color);
|
||||
border-right: 2px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
width: 60%;
|
||||
height: 80vh;
|
||||
height: max-content;
|
||||
margin: 20px 0 0 0;
|
||||
|
||||
&__message {
|
||||
font-size: 16px;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,44 @@
|
|||
@import '../../partials/variables';
|
||||
@import '../../partials/colors';
|
||||
@import '../../lib/functions';
|
||||
@import '../../elements/button/';
|
||||
|
||||
.post {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
width: 80%;
|
||||
height: 90%;
|
||||
width: 35%;
|
||||
|
||||
@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;
|
||||
padding: 0 0 20px 0;
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
background-color: var(--background-color);
|
||||
|
||||
border-radius: 0.25em;
|
||||
cursor: initial;
|
||||
|
||||
|
||||
&__container {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -23,8 +47,6 @@
|
|||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
|
|
@ -33,8 +55,15 @@
|
|||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
|
||||
padding: 20px 50px 0;
|
||||
padding: 20px 0;
|
||||
gap: 20px;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
justify-content: space-between;
|
||||
flex-direction: row-reverse;
|
||||
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__heading {
|
||||
|
|
@ -42,11 +71,18 @@
|
|||
flex-direction: column;
|
||||
padding: 20px 0 10px 0;
|
||||
|
||||
width: 75%;
|
||||
@media (min-width: $hd-breakpoint) {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
@media (max-width: $hd-breakpoint) {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: $header-size;
|
||||
font-size: map-deep-get($post, "header-font-size");
|
||||
|
||||
&--read {
|
||||
color: var(--read-color);
|
||||
|
|
@ -62,8 +98,6 @@
|
|||
}
|
||||
|
||||
&__rule, &__category {
|
||||
background-color: var(--lightest-accent-color) !important;
|
||||
|
||||
& a {
|
||||
color: var(--font-color);
|
||||
}
|
||||
|
|
@ -74,7 +108,15 @@
|
|||
flex-direction: column;
|
||||
|
||||
padding: 10px 0 30px 0;
|
||||
width: 75%;
|
||||
|
||||
@media (min-width: $hd-breakpoint) {
|
||||
width: 72%;
|
||||
}
|
||||
|
||||
@media (max-width: $hd-breakpoint) {
|
||||
width: 90%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
& p {
|
||||
padding: 10px 0;
|
||||
|
|
@ -98,19 +140,66 @@
|
|||
|
||||
&__close-button {
|
||||
background-color: var(--info-color);
|
||||
color: var(--font-color);
|
||||
color: $white;
|
||||
|
||||
& i {
|
||||
padding: 0 $fa-padding 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__meta-info {
|
||||
&__meta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
margin: 15px 0;
|
||||
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 {
|
||||
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 {
|
||||
& .link {
|
||||
color: var(--font-color);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-breakpoint){
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
.posts {
|
||||
margin: 0 0 2% 20px;
|
||||
@import '../../partials/variables';
|
||||
@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 {
|
||||
display: flex;
|
||||
|
|
@ -11,6 +19,10 @@
|
|||
padding: 0;
|
||||
|
||||
list-style: none;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
|
|
@ -21,12 +33,22 @@
|
|||
|
||||
max-width: max-content;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding: 0 10px 10px 10px;
|
||||
}
|
||||
|
||||
& .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 {
|
||||
|
|
@ -38,7 +60,7 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: $header-size;
|
||||
font-size: map-deep-get($post, header-font-size);
|
||||
|
||||
&--read {
|
||||
color: var(--read-color);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../partials/variables';
|
||||
|
||||
.rules {
|
||||
padding: 0;
|
||||
|
||||
|
|
@ -6,19 +8,27 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
border-bottom-right-radius: .25em;
|
||||
border-top-right-radius: .25em;
|
||||
|
||||
padding: 5px 5px 5px 20px;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
margin: 10px 0;
|
||||
padding: 25px 5px 20px 5px;
|
||||
}
|
||||
|
||||
& * {
|
||||
padding: 0 2px 0 2px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--lighter-accent-color);
|
||||
background-color: var(--selected-color);
|
||||
}
|
||||
|
||||
&--selected {
|
||||
background-color: var(--lighter-accent-color);
|
||||
background-color: var(--selected-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../partials/variables';
|
||||
|
||||
.scroll-to-top {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
|
@ -8,6 +10,10 @@
|
|||
|
||||
margin: 0 0 20px 0;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -16,7 +22,7 @@
|
|||
font-style: initial;
|
||||
padding: 10px;
|
||||
|
||||
background-color: var(--lightest-accent-color);
|
||||
background-color: var(--background-color-secondary);
|
||||
|
||||
&--top:before {
|
||||
@include font-awesome;
|
||||
|
|
|
|||
|
|
@ -1,31 +1,77 @@
|
|||
@import '../../partials/variables';
|
||||
@import '../../lib/functions';
|
||||
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-self: start;
|
||||
display: none; // hide the sidebar by default, homepage enables it by default
|
||||
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
--easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--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 {
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: scroll;
|
||||
|
||||
background-color: var(--background-color);
|
||||
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
&__close {
|
||||
display: none;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
&__item {
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
border-bottom-right-radius: .25em;
|
||||
border-top-right-radius: .25em;
|
||||
|
||||
padding: 5px;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
padding: 25px 5px;
|
||||
}
|
||||
|
||||
&--selected, &:hover {
|
||||
background-color: var(--lighter-accent-color);
|
||||
background-color: var(--selected-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +97,60 @@
|
|||
}
|
||||
|
||||
.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 {
|
||||
&__heading {
|
||||
padding: 15px;
|
||||
|
||||
&__heading, &__item {
|
||||
padding: 10px;
|
||||
|
||||
&--select {
|
||||
width: 5%;
|
||||
|
||||
& .checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--name {
|
||||
|
|
@ -10,10 +20,18 @@
|
|||
|
||||
&--category {
|
||||
width: 15%;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--url {
|
||||
width: 40%;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--succeeded {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
table-layout: fixed;
|
||||
|
||||
background-color: var(--background-color);
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
|
||||
text-align: left;
|
||||
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
&__heading {
|
||||
@extend .h1;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__row {
|
||||
|
|
@ -27,7 +31,7 @@
|
|||
}
|
||||
|
||||
&__footer {
|
||||
width: 80%;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
@import '../../partials/variables';
|
||||
@import '../../partials/colors';
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
|
||||
|
|
@ -6,7 +9,12 @@
|
|||
|
||||
text-align: center;
|
||||
|
||||
background-color: var(--lighter-accent-color);
|
||||
background-color: var(--background-color-secondary);
|
||||
|
||||
font-size: small;
|
||||
border-radius: 0.25em;
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
.button {
|
||||
@mixin button {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
|
@ -7,16 +7,15 @@
|
|||
@include button-padding;
|
||||
|
||||
border: none;
|
||||
|
||||
font-size: 16px;
|
||||
border-radius: 0.25em;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&--success, &--confirm {
|
||||
color: var(--confirm-button-font-color) !important;
|
||||
background-color: var(--confirm-color);
|
||||
color: $white !important;
|
||||
}
|
||||
|
||||
&--error, &--cancel {
|
||||
|
|
@ -40,6 +39,15 @@
|
|||
|
||||
&--disabled {
|
||||
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 {
|
||||
@extend .button;
|
||||
|
||||
color: var(--confirm-button-font-color);
|
||||
color: var(--confirm-font-color);
|
||||
|
||||
background-color: var(--confirm-color);
|
||||
|
||||
& i {
|
||||
padding: 0 $fa-padding 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: $mobile-breakpoint) {
|
||||
width: max-content;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
display: block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin: 0 0 0 20px;
|
||||
|
||||
|
||||
& input[type=checkbox] {
|
||||
position: absolute;
|
||||
|
|
@ -14,7 +12,7 @@
|
|||
|
||||
&:checked + .checkbox__label {
|
||||
.checkbox__box {
|
||||
background-color: var(--lightest-accent-color);
|
||||
background-color: var(--info-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +27,7 @@
|
|||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 2px solid var(--lighter-accent-color);
|
||||
border: 1.5px solid var(--border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
.help-text {
|
||||
@extend .small;
|
||||
|
||||
padding: 5px 15px;
|
||||
padding: 10px 0;
|
||||
|
||||
}
|
||||
|
||||
.helptext {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,8 @@
|
|||
@include text-padding;
|
||||
|
||||
color: var(--font-color);
|
||||
background-color: var(--accent-color);
|
||||
border: 1px var(--lighter-accent-color) solid;
|
||||
|
||||
&:focus {
|
||||
border: 1px var(--lightest-accent-color) solid;
|
||||
}
|
||||
background-color: var(--background-color-secondary);
|
||||
border: 1px var(--border-color) solid;
|
||||
|
||||
&[type="file"] {
|
||||
width: 40%;
|
||||
|
|
@ -15,7 +11,6 @@
|
|||
|
||||
&[type="checkbox"] {
|
||||
align-self: flex-start;
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.label {
|
||||
@include text-padding;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: initial;
|
||||
width: 100%;
|
||||
|
||||
margin: 20px 0 0 0;
|
||||
background-color: initial;
|
||||
|
||||
display: grid;
|
||||
|
||||
@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 {
|
||||
margin: 5% auto;
|
||||
width: 50%;
|
||||
|
||||
& .form {
|
||||
@extend .form;
|
||||
|
||||
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 {
|
||||
margin: 0;
|
||||
|
|
@ -21,9 +34,6 @@
|
|||
|
||||
&__fieldset {
|
||||
@extend .form__fieldset;
|
||||
|
||||
&--last {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
#rules--page {
|
||||
& .table {
|
||||
width: 100%;
|
||||
}
|
||||
// TODO: remove scss
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
&__section {
|
||||
&--last {
|
||||
& .fieldset {
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,64 @@
|
|||
$orange: rgba(255, 212, 153, 1);
|
||||
$green: rgba(89, 181, 128, 1);
|
||||
$red: lighten(rgba(231, 76, 60, 1), 10%);
|
||||
$gray: rgba(227, 227, 227, 1);
|
||||
$blue: rgba(111, 164, 196, 1);
|
||||
$orange: #ff2a51;
|
||||
$green: #007936;
|
||||
$red: #d30038;
|
||||
$blue: #0085f2;
|
||||
|
||||
$white: rgba(255, 255, 255, 1);
|
||||
$black: rgba(0, 0, 0, 1);
|
||||
$dark: rgba(0, 0, 0, 0.4);
|
||||
|
||||
$reddit-orange: rgba(255, 69, 0, 1);
|
||||
$white: #fff;
|
||||
$black: #000;
|
||||
|
||||
$transparant-red: transparentize($red, 0.8);
|
||||
$transparant-blue: transparentize($blue, 0.8);
|
||||
$transparant-orange: transparentize($orange, 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
|
||||
$background-color: rgba(255, 249, 176, 1);
|
||||
$background-color: $white;
|
||||
$background-color-secondary: #f9f9fb;
|
||||
|
||||
$font-color: rgba(83, 87, 91, 1);
|
||||
$link-color: rgba(45, 142, 202, 1);
|
||||
$read-color: darken($gainsboro, 10%);
|
||||
$confirm-button-font-color: rgba(255, 255, 255, 1);
|
||||
$font-color: #1b1b1b;
|
||||
|
||||
$accent-color: rgba(255, 171, 115, 1);
|
||||
$lighter-accent-color: rgba(255, 211, 132, 1);
|
||||
$lightest-accent-color: rgba(255, 174, 192, 1);
|
||||
$link-color: #0069c2;
|
||||
$selected-color: #0085f230;
|
||||
$read-color: darken($font-color, 10%);
|
||||
|
||||
$confirm-color: rgba(117, 207, 184, 1);
|
||||
$danger-color: rgba(237, 118, 105, 1);
|
||||
$warning-color: rgba(255, 218, 119, 1);
|
||||
$info-color: rgba(162, 213, 242, 1);
|
||||
$confirm-color: $green;
|
||||
$confirm-font-color: $white;
|
||||
|
||||
$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-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-read-color: darken($dark-font-color, 20%);
|
||||
$dark-confirm-button-font-color: $dark-font-color;
|
||||
$dark-read-color: darken($dark-font-color, 5%);
|
||||
|
||||
$dark-accent-color: rgba(19, 59, 92, 1);
|
||||
$dark-lighter-accent-color: rgba(30, 95, 116, 1);
|
||||
$dark-lightest-accent-color: rgba(88, 61, 114, 1);
|
||||
$dark-confirm-color: $green;
|
||||
$dark-confirm-font-color: $white;
|
||||
|
||||
$dark-confirm-color: rgba(0, 121, 101, 1);
|
||||
$dark-danger-color: rgba(175, 45, 45, 1);
|
||||
$dark-warning-color: rgba(238, 187, 77, 1);
|
||||
$dark-info-color: rgba(31, 111, 139, 1);
|
||||
$dark-danger-color: $red;
|
||||
$dark-danger-font-color: $white;
|
||||
|
||||
$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-family: Rubik;
|
||||
src: url('../assets/fonts/Rubik-Regular.ttf');
|
||||
font-family: Inter;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
src: url('../assets/fonts/Inter-VariableFont_opsz,wght.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Rubik;
|
||||
src: url('../assets/fonts/Rubik-Bold.ttf');
|
||||
font-weight: bold;
|
||||
font-family: Inter;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
src: url('../assets/fonts/Inter-Italic-VariableFont_opsz,wght.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
|
|||
|
|
@ -1,33 +1,37 @@
|
|||
:root {
|
||||
--background-color: #{$background-color};
|
||||
--background-color-secondary: #{$background-color-secondary};
|
||||
|
||||
--font-color: #{$font-color};
|
||||
--link-color: #{$link-color};
|
||||
--selected-color: #{$selected-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-font-color: #{$confirm-font-color};
|
||||
|
||||
--danger-color: #{$danger-color};
|
||||
--danger-font-color: #{$danger-color};
|
||||
|
||||
--warning-color: #{$warning-color};
|
||||
--warning-font-color: #{$warning-color};
|
||||
|
||||
--info-color: #{$info-color};
|
||||
--info-font-color: #{$info-color};
|
||||
|
||||
--border-color: #{$border-color};
|
||||
|
||||
&.dark-theme {
|
||||
--background-color: #{$dark-background-color};
|
||||
--background-color-secondary: #{$dark-background-color-secondary};
|
||||
|
||||
--font-color: #{$dark-font-color};
|
||||
--link-color: #{$dark-link-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-font-color: #{$dark-confirm-font-color};
|
||||
|
||||
--danger-color: #{$dark-danger-color};
|
||||
--warning-color: #{$dark-warning-color};
|
||||
--info-color: #{$dark-info-color};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,36 @@
|
|||
$fa-padding: 7px;
|
||||
|
||||
$header-size: 1.2em;
|
||||
// Fonts
|
||||
$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>
|
||||
<head>
|
||||
<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" />
|
||||
{% block head %}
|
||||
<link href="{% static 'css/main.css' %}" rel="stylesheet" />
|
||||
|
|
@ -11,22 +12,14 @@
|
|||
</head>
|
||||
|
||||
<body class="body">
|
||||
<input id="menu-input" type="checkbox" />
|
||||
|
||||
<nav class="nav">
|
||||
<ol>
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="nav__item"><a href="{% url 'index' %}">Home</a></li>
|
||||
<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>
|
||||
<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>
|
||||
<div class="menu">
|
||||
<label class="menu__icon fas fa-bars" for="menu-input" />
|
||||
</div>
|
||||
|
||||
{% include "components/nav-list/nav-list.html" with request=request only %}
|
||||
|
||||
<i class="theme-switcher fas fa-adjust"></i>
|
||||
</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