Merge static refactor

This commit is contained in:
sonny 2020-02-22 22:44:09 +01:00
parent 5976870d38
commit 3045702a1e
158 changed files with 549 additions and 757 deletions

2
.gitignore vendored
View file

@ -197,5 +197,7 @@ src/newsreader/fixtures/local
# Javascript
node_modules/
static/
# Css
*.css

View file

@ -1,52 +0,0 @@
import path from 'path';
import { dest, series } from 'gulp';
import babelify from 'babelify';
import browserify from 'browserify';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import concat from 'gulp-concat';
const PROJECT_DIR = path.join('src', 'newsreader');
const SRC_DIR = path.join(PROJECT_DIR, 'js');
const STATIC_SUFFIX = 'dist/js/';
const CORE_DIR = path.join(PROJECT_DIR, 'news', 'core', 'static', 'core');
const COLLECTION_DIR = path.join(
PROJECT_DIR,
'news',
'collection',
'static',
'collection'
);
const taskMappings = [
{ name: 'homepage', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'categories', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'rules', destDir: `${COLLECTION_DIR}/${STATIC_SUFFIX}` },
];
const babelTask = done => {
const tasks = taskMappings.map(taskMapping => {
const { name, destDir } = taskMapping;
const bundle = browserify({
entries: `${SRC_DIR}/pages/${name}/index.js`,
debug: true,
});
const transpiledBundle = bundle.transform(babelify);
return () =>
transpiledBundle
.bundle()
.pipe(source('index.js'))
.pipe(buffer())
.pipe(concat(`${name}.js`))
.pipe(dest(destDir));
});
return series(...tasks)(done);
};
export default babelTask;

View file

@ -1,46 +0,0 @@
import { dest, series, src } from 'gulp';
import concat from 'gulp-concat';
import path from 'path';
import sass from 'gulp-sass';
const PROJECT_DIR = path.join('src', 'newsreader');
const SRC_DIR = path.join(PROJECT_DIR, 'scss');
const STATIC_SUFFIX = 'dist/css/';
export const ACCOUNTS_DIR = path.join(PROJECT_DIR, 'accounts', 'static', 'accounts');
export const CORE_DIR = path.join(PROJECT_DIR, 'news', 'core', 'static', 'core');
export const COLLECTION_DIR = path.join(
PROJECT_DIR,
'news',
'collection',
'static',
'collection'
);
const taskMappings = [
{ name: 'login', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` },
{ name: 'register', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` },
{ name: 'activate', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` },
{ name: 'password-reset', destDir: `${ACCOUNTS_DIR}/${STATIC_SUFFIX}` },
{ name: 'homepage', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'categories', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'category', destDir: `${CORE_DIR}/${STATIC_SUFFIX}` },
{ name: 'rules', destDir: `${COLLECTION_DIR}/${STATIC_SUFFIX}` },
{ name: 'rule', destDir: `${COLLECTION_DIR}/${STATIC_SUFFIX}` },
{ name: 'import', destDir: `${COLLECTION_DIR}/${STATIC_SUFFIX}` },
];
export const sassTask = done => {
const tasks = taskMappings.map(taskMapping => {
const { name, destDir } = taskMapping;
return () =>
src(`${SRC_DIR}/pages/${name}/index.scss`)
.pipe(sass().on('error', sass.logError))
.pipe(concat(`${name}.css`))
.pipe(dest(destDir));
});
series(...tasks)(done);
};

View file

@ -1,26 +1,70 @@
import { parallel, series, watch as _watch } from 'gulp';
import path from 'path';
import del from 'del';
import { ACCOUNTS_DIR, CORE_DIR, sassTask } from './gulp/sass';
import babelTask from './gulp/babel';
import { dest, parallel, series, src, watch as _watch } from 'gulp';
import concat from 'gulp-concat';
import globby from 'globby';
import sass from 'gulp-sass';
import babelify from 'babelify';
import browserify from 'browserify';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
const PROJECT_DIR = path.join('src', 'newsreader');
const STATIC_DIR = path.join(PROJECT_DIR, 'static');
const CSS_SUFFIX = 'css/';
const JS_SUFFIX = 'js/';
const SASS_DEST_DIR = path.join(STATIC_DIR, CSS_SUFFIX);
const JS_DEST_DIR = path.join(STATIC_DIR, JS_SUFFIX);
const SASS_DIR = path.join(PROJECT_DIR, 'scss');
const JS_DIR = path.join(PROJECT_DIR, 'js');
const clean = () => {
return del([
`${ACCOUNTS_DIR}/accounts/dist/css/*`,
return del([`${STATIC_DIR}/${CSS_SUFFIX}/*.css`, `${STATIC_DIR}/${JS_SUFFIX}/*.js`]);
};
`${CORE_DIR}/core/dist/css/*`,
`${CORE_DIR}/core/dist/js/*`,
]);
const sassTask = () => {
return src(`${SASS_DIR}/index.scss`)
.pipe(sass().on('error', sass.logError))
.pipe(concat(`main.css`))
.pipe(dest(SASS_DEST_DIR));
};
const babelTask = () => {
const getDirName = filename => {
const fragments = filename.split('/');
return fragments[fragments.length - 2];
};
const promise = globby([`${JS_DIR}/pages/**/index.js`]).then(entries => {
entries.forEach(entry => {
const bundle = browserify({ entries: entry, debug: true });
const transpiledBundle = bundle.transform(babelify);
const bundleName = getDirName(entry);
return transpiledBundle
.bundle()
.pipe(source('index.js'))
.pipe(buffer())
.pipe(concat(`${bundleName}.js`))
.pipe(dest(JS_DEST_DIR));
});
});
return Promise.resolve(promise);
};
export const watch = () => {
return _watch([`${PROJECT_DIR}/scss/**/*.scss`, `${PROJECT_DIR}/js/**/*.js`], done => {
series(clean, sassTask, babelTask)(done);
});
return _watch(
[`${PROJECT_DIR}/scss/**/*.scss`, `${PROJECT_DIR}/js/**/*.js`],
{ ignoreInitial: false },
done => {
series(clean, parallel(sassTask, babelTask))(done);
}
);
};
export default series(clean, sassTask, babelTask);
export default series(clean, parallel(babelTask, sassTask));

85
package-lock.json generated
View file

@ -1203,7 +1203,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
"integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "2.0.3",
"run-parallel": "^1.1.9"
@ -1212,14 +1211,12 @@
"@nodelib/fs.stat": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz",
"integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==",
"dev": true
"integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA=="
},
"@nodelib/fs.walk": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz",
"integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==",
"dev": true,
"requires": {
"@nodelib/fs.scandir": "2.1.3",
"fastq": "^1.6.0"
@ -1315,9 +1312,9 @@
"dev": true
},
"@types/node": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.2.tgz",
"integrity": "sha512-B8emQA1qeKerqd1dmIsQYnXi+mmAzTB7flExjmy5X1aVAKFNNNDubkavwR13kR6JnpeLp3aLoJhwn9trWPAyFQ==",
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz",
"integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==",
"dev": true
},
"@types/stack-utils": {
@ -1641,8 +1638,7 @@
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="
},
"array-unique": {
"version": "0.3.2",
@ -2932,6 +2928,22 @@
"slash": "^3.0.0"
},
"dependencies": {
"globby": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz",
"integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==",
"dev": true,
"requires": {
"@types/glob": "^7.1.1",
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.0.3",
"glob": "^7.1.3",
"ignore": "^5.1.1",
"merge2": "^1.2.3",
"slash": "^3.0.0"
}
},
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
@ -3018,7 +3030,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"requires": {
"path-type": "^4.0.0"
},
@ -3026,8 +3037,7 @@
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
}
}
},
@ -3496,7 +3506,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz",
"integrity": "sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@ -3509,7 +3518,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
@ -3518,7 +3526,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
@ -3527,7 +3534,6 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
"integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
@ -3535,14 +3541,12 @@
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"micromatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
"dev": true,
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.0.5"
@ -3552,7 +3556,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
@ -3581,7 +3584,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz",
"integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==",
"dev": true,
"requires": {
"reusify": "^1.0.0"
}
@ -4511,18 +4513,15 @@
"dev": true
},
"globby": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz",
"integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==",
"dev": true,
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz",
"integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==",
"requires": {
"@types/glob": "^7.1.1",
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.0.3",
"glob": "^7.1.3",
"ignore": "^5.1.1",
"merge2": "^1.2.3",
"fast-glob": "^3.1.1",
"ignore": "^5.1.4",
"merge2": "^1.3.0",
"slash": "^3.0.0"
}
},
@ -4869,8 +4868,7 @@
"ignore": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
"integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==",
"dev": true
"integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A=="
},
"import-local": {
"version": "2.0.0",
@ -5089,8 +5087,7 @@
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
},
"is-finite": {
"version": "1.0.2",
@ -5117,7 +5114,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
@ -6527,8 +6523,7 @@
"merge2": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz",
"integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==",
"dev": true
"integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw=="
},
"micromatch": {
"version": "3.1.10",
@ -7376,10 +7371,9 @@
"dev": true
},
"picomatch": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz",
"integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==",
"dev": true
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz",
"integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA=="
},
"pify": {
"version": "4.0.1",
@ -8057,8 +8051,7 @@
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
},
"rimraf": {
"version": "3.0.0",
@ -8088,8 +8081,7 @@
"run-parallel": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
"dev": true
"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q=="
},
"safe-buffer": {
"version": "5.1.2",
@ -8299,8 +8291,7 @@
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
},
"snapdragon": {
"version": "0.8.2",

View file

@ -17,6 +17,7 @@
"author": "Sonny",
"license": "GPL-3.0-or-later",
"dependencies": {
"globby": "^11.0.0",
"js-cookie": "^2.2.1",
"lodash": "^4.17.15",
"object-assign": "^4.1.1",

View file

@ -2,12 +2,8 @@
{% load static %}
{% block head %}
<link href="{% static 'accounts/dist/css/login.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<main class="main">
<main id="login--page" class="main">
<form class="form login-form" method="POST" action="{% url 'accounts:login' %}">
{% csrf_token %}
<div class="form__header">

View file

@ -116,7 +116,7 @@ USE_TZ = True
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = "/static/"
STATICFILES_DIRS = ["src/newsreader/static/icons"]
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
# https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-STATICFILES_FINDERS
STATICFILES_FINDERS = [

View file

@ -1,7 +1,11 @@
import React from 'react';
const Modal = props => {
return <div className="modal">{props.content}</div>;
return (
<div className="modal">
<div className="modal__item">{props.content}</div>
</div>
);
};
export default Modal;

View file

@ -0,0 +1,3 @@
import './pages/homepage/index.js';
import './pages/rules/index.js';
import './pages/categories/index.js';

View file

@ -6,7 +6,7 @@ const CategoryCard = props => {
const { category } = props;
const categoryRules = category.rules.map(rule => {
const faviconUrl = rule.favicon ? rule.favicon : '/static/picture.svg';
const faviconUrl = rule.favicon ? rule.favicon : '/static/icons/picture.svg';
return (
<li key={rule.pk} className="list__item">

View file

@ -4,12 +4,12 @@ import Modal from '../../../components/Modal.js';
const CategoryModal = props => {
const content = (
<div className="category-modal">
<div className="category-modal__header">
<h1 className="h1 category-modal__title">Delete category</h1>
<>
<div className="modal__header">
<h1 className="h1 modal__title">Delete category</h1>
</div>
<div className="category-modal__content">
<div className="modal__content">
<p className="p">Are you sure you want to delete {props.category.name}?</p>
<small className="small">
Collection rules coupled to this category will not be deleted but will have no
@ -17,7 +17,7 @@ const CategoryModal = props => {
</small>
</div>
<div className="category-modal__footer">
<div className="modal__footer">
<button className="button button--confirm" onClick={props.handleCancel}>
Cancel
</button>
@ -28,7 +28,7 @@ const CategoryModal = props => {
Delete category
</button>
</div>
</div>
</>
);
return <Modal content={content} />;

View file

@ -3,10 +3,8 @@ import ReactDOM from 'react-dom';
import App from './App.js';
const content = document.getElementById('categories--page');
const dataScript = document.getElementById('categories-data');
const categories = JSON.parse(dataScript.textContent);
ReactDOM.render(
<App categories={categories} />,
document.getElementsByClassName('content')[0]
);
ReactDOM.render(<App categories={categories} />, content);

View file

@ -17,7 +17,6 @@ class App extends React.Component {
render() {
return (
<>
<main className="main">
<Sidebar />
<FeedList />
@ -28,7 +27,6 @@ class App extends React.Component {
category={this.props.category}
/>
)}
</main>
</>
);
}

View file

@ -6,6 +6,7 @@ import { unSelectPost, markPostRead } from '../actions/posts.js';
import { formatDatetime } from '../../../utils.js';
class PostModal extends React.Component {
modalListener = ::this.modalListener;
readTimer = null;
componentDidMount() {
@ -16,6 +17,8 @@ class PostModal extends React.Component {
if (!post.read) {
this.readTimer = setTimeout(markPostRead, 3000, post, token);
}
window.addEventListener('click', this.modalListener);
}
componentWillUnmount() {
@ -24,6 +27,16 @@ class PostModal extends React.Component {
}
this.readTimer = null;
window.removeEventListener('click', this.modalListener);
}
modalListener(e) {
const targetClassName = e.target.className;
if (this.props.post && targetClassName == 'modal post-modal') {
this.props.unSelectPost();
}
}
render() {
@ -32,7 +45,7 @@ class PostModal extends React.Component {
const titleClassName = post.read ? 'post__title post__title--read' : 'post__title';
return (
<div className="modal">
<div className="modal post-modal">
<div className="post">
<button
className="button post__close-button"
@ -40,7 +53,7 @@ class PostModal extends React.Component {
>
Close{' '}
<span>
<img src="/static/cross.svg" width="20" />
<img src="/static/icons/cross.svg" width="20" />
</span>
</button>
<div className="post__header">
@ -53,7 +66,7 @@ class PostModal extends React.Component {
target="_blank"
rel="noopener noreferrer"
>
<img src="/static/link.svg" />
<img src="/static/icons/link.svg" />
</a>
</div>
</div>

View file

@ -52,7 +52,7 @@ class FeedList extends React.Component {
return (
<div className="post-message">
<div className="post-message__block">
<img src="/static/arrow-left.svg" height="28" width="28" />
<img src="/static/icons/arrow-left.svg" height="28" width="28" />
<p className="post-message__text">
Select a category or rule to show its unread posts
</p>

View file

@ -34,7 +34,7 @@ class PostItem extends React.Component {
target="_blank"
rel="noopener noreferrer"
>
<img src="/static/link.svg" />
<img src="/static/icons/link.svg" />
</a>
</div>
</li>

View file

@ -25,8 +25,8 @@ class CategoryItem extends React.Component {
render() {
const imageSrc = this.state.open
? '/static/chevron-down.svg'
: '/static/chevron-right.svg';
? '/static/icons/chevron-down.svg'
: '/static/icons/chevron-right.svg';
const selected = isSelected(this.props.category, this.props.selected, CATEGORY_TYPE);
const className = selected ? 'category category--selected' : 'category';
@ -35,7 +35,7 @@ class CategoryItem extends React.Component {
});
return (
<li className="categories__item">
<li className="sidebar__item">
<div className={className}>
<div className="category__menu" onClick={() => this.toggleRules()}>
<img src={imageSrc} width="20" heigth="20" />

View file

@ -21,15 +21,15 @@ class RuleItem extends React.Component {
const className = `rules__item ${selected ? 'rules__item--selected' : ''}`;
const favicon = this.props.rule.favicon
? this.props.rule.favicon
: '/static/picture.svg';
: '/static/icons/picture.svg';
return (
<li className={className} onClick={() => this.handleSelect()}>
<div className="rule">
<div className="rules__info">
<span>
<img className="icon" width="20" src={favicon} />
</span>
<h5 className="rule__title" title={this.props.rule.name}>
<h5 className="rules__title" title={this.props.rule.name}>
{this.props.rule.name}
</h5>
</div>

View file

@ -28,13 +28,11 @@ class Sidebar extends React.Component {
return (
<div className="sidebar">
<nav className="categories">
{(this.props.categories.isFetching || this.props.rules.isFetching) && (
<LoadingIndicator />
)}
<ul>{items}</ul>
</nav>
<ul className="sidebar__nav">{items}</ul>
{!isEqual(this.props.selected.item, {}) && <ReadButton />}
</div>

View file

@ -6,11 +6,12 @@ import configureStore from './configureStore.js';
import App from './App.js';
const content = document.getElementById('homepage--page');
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementsByClassName('content')[0]
content
);

View file

@ -5,10 +5,10 @@ import Card from '../../../components/Card.js';
const RuleCard = props => {
const { rule } = props;
const faviconUrl = rule.favicon ? rule.favicon : '/static/picture.svg';
const stateIcon = rule.succeeded
? '/static/checkmark-circle.svg'
: '/static/warning.svg';
const faviconUrl = rule.favicon ? rule.favicon : '/static/icons/picture.svg';
const stateIcon = !rule.error
? '/static/icons/checkmark-circle.svg'
: '/static/icons/warning.svg';
const cardHeader = (
<>
@ -23,20 +23,21 @@ const RuleCard = props => {
const cardContent = (
<>
<ul className="list rules">
<li className="list__item rules__item">{rule.category}</li>
<li className="list__item rules__item">
<a className="link" target="_blank" rel="noopener noreferrer" href={rule.url}>
{rule.url}
</a>
</li>
<li className="list__item rules__item">{rule.created}</li>
<li className="list__item rules__item">{rule.timezone}</li>
</ul>
{!rule.succeeded && (
{rule.error && (
<ul className="list errorlist">
<li className="list__item errorlist__item">{rule.error}</li>
</ul>
)}
{rule.category && <li className="list__item">{rule.category}</li>}
<li className="list__item">
<a className="link" target="_blank" rel="noopener noreferrer" href={rule.url}>
{rule.url}
</a>
</li>
<li className="list__item">{rule.created}</li>
<li className="list__item">{rule.timezone}</li>
</ul>
</>
);

View file

@ -4,16 +4,17 @@ import Modal from '../../../components/Modal.js';
const RuleModal = props => {
const content = (
<div className="rule-modal">
<div className="rule-modal__header">
<h1 className="h1 rule-modal__title">Delete rule</h1>
<>
<div>
<div className="modal__header">
<h1 className="h1 modal__title">Delete rule</h1>
</div>
<div className="rule-modal__content">
<div className="modal__content">
<p className="p">Are you sure you want to delete {props.rule.name}?</p>
</div>
<div className="rule-modal__footer">
<div className="modal__footer">
<button className="button button--confirm" onClick={props.handleCancel}>
Cancel
</button>
@ -25,6 +26,7 @@ const RuleModal = props => {
</button>
</div>
</div>
</>
);
return <Modal content={content} />;

View file

@ -3,7 +3,8 @@ import ReactDOM from 'react-dom';
import App from './App.js';
const content = document.getElementById('rules--page');
const dataScript = document.getElementById('rules-data');
const rules = JSON.parse(dataScript.textContent);
ReactDOM.render(<App rules={rules} />, document.getElementsByClassName('content')[0]);
ReactDOM.render(<App rules={rules} />, content);

View file

@ -1,11 +1,17 @@
from django import forms
import pytz
from newsreader.news.collection.models import CollectionRule
from newsreader.news.core.models import Category
class CollectionRuleForm(forms.ModelForm):
category = forms.ModelChoiceField(required=False, queryset=Category.objects.all())
timezone = forms.ChoiceField(
widget=forms.Select(attrs={"size": len(pytz.all_timezones)}),
choices=((timezone, timezone) for timezone in pytz.all_timezones),
)
def __init__(self, *args, **kwargs) -> None:
self.user = kwargs.pop("user")

View file

@ -2,12 +2,8 @@
{% load static i18n %}
{% block head %}
<link href="{% static 'collection/dist/css/import.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content">
<main id="import--page" class="main">
<form class="form import-form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.non_field_errors }}
@ -37,5 +33,5 @@
</fieldset>
</section>
</form>
</div>
</main>
{% endblock %}

View file

@ -4,28 +4,6 @@
<h1 class="h1 form__title">Create a rule</h1>
{% endblock %}
{% block name-input %}
<input class="input rule-form__input" type="text" name="name" required />
{% endblock %}
{% block category-input %}
<option class="option rule-form__option" value="{{ category.pk }}">
{{ category.name }}
</option>
{% endblock %}
{% block url-input %}
<input class="input rule-form__input" type="url" name="url" required />
{% endblock %}
{% block favicon-input %}
<input class="input rule-form__input" type="url" name="favicon" />
{% endblock %}
{% block timezone-input %}
<option class="option rule-form__option">{{ timezone }}</option>
{% endblock %}
{% block confirm-button %}
<button class="button button--confirm">Create rule</button>
{% endblock %}

View file

@ -4,31 +4,6 @@
<h1 class="h1 form__title">Update rule</h1>
{% endblock %}
{% block name-input %}
<input class="input rule-form__input" type="text" name="name"
value="{{ rule.name }}" required />
{% endblock %}
{% block category-input %}
<option class="option rule-form__option" value="{{ category.pk }}" {% if rule.category and rule.category.pk == category.pk %}selected{% endif %}>
{{ category.name }}
</option>
{% endblock %}
{% block url-input %}
<input class="input rule-form__input" type="url" name="url" value="{{ rule.url }}" required />
{% endblock %}
{% block favicon-input %}
<input class="input rule-form__input" type="url" value="{{ rule.favicon|default:"" }}" name="favicon" />
{% endblock %}
{% block timezone-input %}
<option class="option rule-form__option" {% if rule.timezone == timezone %}selected{% endif %}>
{{ timezone }}
</option>
{% endblock %}
{% block confirm-button %}
<button class="button button--confirm">Save rule</button>
{% endblock %}

View file

@ -2,12 +2,8 @@
{% load static %}
{% block head %}
<link href="{% static 'collection/dist/css/rule.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content">
<main id="rule--page" class="main">
<form class="form rule-form" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
@ -17,45 +13,34 @@
</div>
<section class="section form__section rule-form__section">
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Name</label>
{% block name-input %}{% endblock %}
<label class="label" for="name">Name</label>
{{ form.name.errors }}
{{ form.name }}
</fieldset>
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Category</label>
<select class="select rule-form__select" name="category">
{% for category in categories %}
{% block category-input %}{% endblock %}
{% endfor %}
</select>
<label class="label" for="name">Category</label>
{{ form.category.errors }}
{{ form.category }}
</fieldset>
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Feed url</label>
{% block url-input %}{% endblock %}
<label class="label" for="name">Feed url</label>
{{ form.url.errors }}
{{ form.url }}
</fieldset>
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Favicon url</label>
{% block favicon-input %}{% endblock %}
<label class="label" for="name">Favicon url</label>
{{ form.favicon.errors }}
{{ form.favicon }}
</fieldset>
<fieldset class="form__fieldset rule-form__fieldset">
<label class="label rule-form__label" for="name">Timezone</label>
<label class="label" for="name">Timezone</label>
<small class="small helptext">The timezone which the feed uses</small>
<select class="select rule-form__select" size="{{ timezones|length }}" name="timezone">
{% for timezone in timezones %}
{% block timezone-input %}{% endblock %}
{% endfor %}
</select>
{{ form.timezone.errors }}
{{ form.timezone }}
</fieldset>
</section>
@ -66,5 +51,5 @@
</fieldset>
</section>
</form>
</div>
</main>
{% endblock %}

View file

@ -2,12 +2,8 @@
{% load static %}
{% block head %}
<link href="{% static 'collection/dist/css/rules.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content"></div>
<main id="rules--page" class="main"></main>
{% endblock %}
{% block scripts %}
@ -19,15 +15,15 @@
"pk": {{ rule.pk }},
"name": "{{ rule.name }}",
"url": "{{ rule.url }}",
"favicon": {% if rule.favicon %}"{{ rule.favicon }}"{% else %}""{% endif %},
"category": "{{ rule.category }}",
"favicon": "{{ rule.favicon|default:'' }}",
"category": "{{ rule.category|default:'' }}",
"timezone": "{{ rule.timezone }}",
"created": "{{ rule.created }}",
"succeeded": {% if rule.succeeded %}true{% else %}false{% endif %},
"error": {% if rule.error %}"{{ rule.error }}"{% else %}""{% endif %}
"error": "{{ rule.error|default:'' }}"
}
{% endfor %}
]
</script>
<script src="{% static 'collection/dist/js/rules.js' %}"></script>
<script src="{% static 'js/rules.js' %}"></script>
{% endblock %}

View file

@ -94,7 +94,7 @@ class NestedPostCategoryView(ListAPIView):
queryset = Post.objects.filter(
rule__in=category.rules.values_list("id", flat=True)
).order_by("rule__name", "-publication_date")
).order_by("rule", "-publication_date")
return queryset

View file

@ -1,5 +1,6 @@
from django import forms
from newsreader.accounts.models import User
from newsreader.news.collection.models import CollectionRule
from newsreader.news.core.models import Category
@ -11,17 +12,23 @@ class CategoryForm(forms.ModelForm):
widget=forms.widgets.CheckboxSelectMultiple,
)
user = forms.ModelChoiceField(
queryset=User.objects.none(),
widget=forms.widgets.HiddenInput(attrs={"readonly": True}),
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
super().__init__(*args, **kwargs)
if self.user:
self.fields["rules"].queryset = CollectionRule.objects.filter(user=self.user)
self.fields["user"].queryset = User.objects.filter(pk=self.user.pk)
self.initial["user"] = self.user
def save(self, commit=True) -> Category:
instance = super().save(commit=False)
instance.user = self.user
if commit:
instance.save()
@ -34,4 +41,4 @@ class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = ("name", "rules")
fields = ("name", "rules", "user")

View file

@ -2,12 +2,8 @@
{% load static %}
{% block head %}
<link href="{% static 'core/dist/css/categories.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content"></div>
<main id="categories--page" class="main"></main>
{% endblock %}
{% block scripts %}
@ -25,7 +21,7 @@
{
"pk": {{ rule.pk }},
"name": "{{ rule.name }}",
"favicon": {% if rule.favicon %}"{{ rule.favicon }}"{% else %}null{% endif %},
"favicon": "{{ rule.favicon|default:'' }}",
"created": "{{ rule.created }}"
}
{% endfor %}
@ -34,5 +30,5 @@
{% endfor %}
]
</script>
<script src="{% static 'core/dist/js/categories.js' %}"></script>
<script src="{% static 'js/categories.js' %}"></script>
{% endblock %}

View file

@ -4,15 +4,6 @@
<h1 class="h1 form__title">Create a category</h1>
{% endblock %}
{% block name-input %}
<input class="input category-form__input" type="text" name="name" required />
{% endblock %}
{% block rule-input %}
<input class="input category-form__input" type="checkbox" name="rules"
value="{{ rule.pk }}" />
{% endblock %}
{% block confirm-button %}
<button class="button button--confirm">Create category</button>
{% endblock %}

View file

@ -4,17 +4,6 @@
<h1 class="h1 form__title">Update category</h1>
{% endblock %}
{% block name-input %}
<input class="input category-form__input" type="text" name="name"
value="{{ category.name }}" required />
{% endblock %}
{% block rule-input %}
<input class="input category-form__input" type="checkbox" name="rules"
{% if rule.pk in category.rule_ids %}checked{% endif %}
value="{{ rule.pk }}" />
{% endblock %}
{% block confirm-button %}
<button class="button button--confirm">Save category</button>
{% endblock %}

View file

@ -2,24 +2,24 @@
{% load static %}
{% block head %}
<link href="{% static 'core/dist/css/category.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content">
<main id="category--page" class="main">
<form class="form category-form" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="form__header">
{% block form-header %}{% endblock %}
</div>
{{ form.non_field_errors }}
{{ form.user.errors }}
{{ form.user }}
<section class="section form__section category-form__section">
<fieldset class="form__fieldset category-form__fieldset">
<label class="label category-form__label" for="name">Name</label>
{% block name-input %}{% endblock %}
{{ form.name.errors }}
{{ form.name }}
</fieldset>
</section>
@ -29,17 +29,21 @@
<small class="small help-text">
Note that existing assigned rules will be reassigned to this category
</small>
{{ form.rules.errors }}
<ul class="list checkbox-list">
{% for rule in rules %}
<li class="list__item checkbox-list__item">
{% block rule-input %}{% endblock %}
<input class="input category-form__input" type="checkbox" name="rules"
{% if category and rule.pk in category.rule_ids %}checked{% endif %}
value="{{ rule.pk }}" />
<img class="favicon"
src="{% if rule.favicon %}{{ rule.favicon }}{% else %}/static/picture.svg{% endif %}" />
src="{% if rule.favicon %}{{ rule.favicon }}{% else %}/static/icons/picture.svg{% endif %}" />
<span>{{ rule.name }}</span>
</li>
{% endfor %}
</ul>
{{ form.rules.errors }}
</fieldset>
</section>
@ -50,5 +54,5 @@
</fieldset>
</section>
</form>
</div>
</main>
{% endblock %}

View file

@ -2,14 +2,10 @@
{% load static %}
{% block head %}
<link href="{% static 'core/dist/css/homepage.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div class="content"></div>
<main id="homepage--page" class="main"></main>
{% endblock %}
{% block scripts %}
<script src="{% static 'core/dist/js/homepage.js' %}"></script>
<script src="{% static 'js/homepage.js' %}"></script>
{% endblock %}

View file

@ -488,11 +488,11 @@ class NestedCategoryPostView(TestCase):
self.assertEquals(posts[0]["title"], "Second BBC post")
self.assertEquals(posts[1]["title"], "First BBC post")
self.assertEquals(posts[2]["title"], "Second Reuters post")
self.assertEquals(posts[3]["title"], "First Reuters post")
self.assertEquals(posts[2]["title"], "Second Guardian post")
self.assertEquals(posts[3]["title"], "First Guardian post")
self.assertEquals(posts[4]["title"], "Second Guardian post")
self.assertEquals(posts[5]["title"], "First Guardian post")
self.assertEquals(posts[4]["title"], "Second Reuters post")
self.assertEquals(posts[5]["title"], "First Reuters post")
def test_only_posts_from_category_are_returned(self):
category = CategoryFactory.create(user=self.user)

View file

@ -27,7 +27,11 @@ class CategoryCreateViewTestCase(CategoryViewTestCase, TestCase):
def test_creation(self):
rules = CollectionRuleFactory.create_batch(size=4, user=self.user)
data = {"name": "new-category", "rules": [rule.pk for rule in rules]}
data = {
"name": "new-category",
"rules": [rule.pk for rule in rules],
"user": self.user.pk,
}
response = self.client.post(self.url, data)
self.assertEquals(response.status_code, 302)
@ -55,13 +59,29 @@ class CategoryCreateViewTestCase(CategoryViewTestCase, TestCase):
size=3, user=self.user, category=None
)
data = {"name": "new-category", "rules": [rule.pk for rule in other_rules]}
data = {
"name": "new-category",
"rules": [rule.pk for rule in other_rules],
"user": self.user.pk,
}
response = self.client.post(self.url, data)
self.assertContains(response, "not one of the available choices")
self.assertEquals(Category.objects.count(), 0)
def test_unique_together(self):
category = CategoryFactory(name="category", user=self.user)
data = {"name": "category", "user": self.user.pk, "rules": []}
response = self.client.post(self.url, data)
categories = Category.objects.all()
self.assertContains(response, "already exists")
self.assertCountEqual(categories, [category])
class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
def setUp(self):
@ -71,7 +91,7 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
self.url = reverse("category-update", args=[self.category.pk])
def test_name_change(self):
data = {"name": "durp"}
data = {"name": "durp", "user": self.user.pk}
self.client.post(self.url, data)
self.category.refresh_from_db()
@ -80,7 +100,11 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
def test_add_collection_rules(self):
rules = CollectionRuleFactory.create_batch(size=4, user=self.user)
data = {"name": self.category.name, "rules": [rule.pk for rule in rules]}
data = {
"name": self.category.name,
"rules": [rule.pk for rule in rules],
"user": self.user.pk,
}
self.client.post(self.url, data)
@ -100,7 +124,11 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
self.assertCountEqual(self.category.rule_ids, [rule.pk for rule in current_rules])
data = {"name": self.category.name, "rules": [rule.pk for rule in other_rules]}
data = {
"name": self.category.name,
"rules": [rule.pk for rule in other_rules],
"user": self.user.pk,
}
self.client.post(self.url, data)
@ -116,7 +144,7 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
self.assertCountEqual(self.category.rule_ids, [rule.pk for rule in current_rules])
data = {"name": "durp"}
data = {"name": "durp", "user": self.user.pk}
self.client.post(self.url, data)
self.category.refresh_from_db()
@ -139,7 +167,7 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
other_category = CategoryFactory(name="other category", user=other_user)
other_category.rules.set([*other_rules])
data = {"name": "durp"}
data = {"name": "durp", "user": other_user.pk}
other_url = reverse("category-update", args=[other_category.pk])
response = self.client.post(other_url, data)
@ -161,7 +189,11 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
self.assertCountEqual(self.category.rule_ids, [rule.pk for rule in current_rules])
data = {"name": self.category.name, "rules": [rule.pk for rule in other_rules]}
data = {
"name": self.category.name,
"rules": [rule.pk for rule in other_rules],
"user": self.user.pk,
}
response = self.client.post(self.url, data)
@ -172,3 +204,16 @@ class CategoryUpdateViewTestCase(CategoryViewTestCase, TestCase):
other_category.refresh_from_db()
self.assertCountEqual(other_category.rule_ids, [rule.pk for rule in other_rules])
def test_unique_together(self):
other_category = CategoryFactory(name="other category", user=self.user)
url = reverse("category-update", args=[other_category.pk])
data = {"name": "category", "user": self.user.pk, "rules": []}
response = self.client.post(url, data)
categories = Category.objects.all()
self.assertContains(response, "already exists")
self.assertCountEqual(categories, [self.category, other_category])

View file

@ -53,9 +53,7 @@ class CategoryDetailMixin:
return context_data
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
return {**super().get_form_kwargs(), "user": self.request.user}
class CategoryListView(CategoryViewMixin, ListView):

View file

@ -1,4 +1,6 @@
.card {
@extend .card;
&__header {
& div {
display: flex;

View file

@ -1 +1,2 @@
@import "card";
@import "rule-card";

View file

@ -1,5 +0,0 @@
.content {
display: flex;
flex-direction: column;
align-items: center;
}

View file

@ -1 +0,0 @@
@import "content";

View file

@ -1,20 +1,23 @@
.errorlist {
@extend .list;
padding: 10px;
margin: 5px 0;
padding: 0;
background-color: $error-red;
color: $white;
list-style: disc;
list-style-position: inside;
& li {
margin: 15px 0 0 0;
&__item {
margin: 10px 0;
padding: 10px;
&:first-child {
margin: 0;
background-color: $error-red;
border-radius: 5px;
}
& li {
@extend .errorlist__item;
}
}

View file

@ -1,8 +1,9 @@
.category-form {
@extend .form;
margin: 20px 0;
&__section:last-child {
& .category-form__fieldset {
display: flex;
flex-direction: row;

View file

@ -2,7 +2,6 @@
margin: 20px 0;
&__section:last-child {
& .rule-form__fieldset {
display: flex;
flex-direction: row;
@ -10,18 +9,17 @@
}
}
&__select[name=category] {
#id_category {
width: 50%;
padding: 0 10px;
}
&__select[name=timezone] {
#id_timezone {
max-height: 200px;
width: 50%;
margin: 0 15px;
padding: 0 10px;
}
}

View file

@ -1 +1,12 @@
@import "form";
@import "category-form";
@import "rule-form";
@import "import-form";
@import "login-form";
@import "activation-form";
@import "register-form";
@import "password-reset-form";
@import "password-reset-confirm-form";

View file

@ -1,13 +1,26 @@
@import "./body/index";
@import "./form/index";
@import "./main/index";
@import "./navbar/index";
@import "./loading-indicator/index";
@import "./modal/index";
@import "./card/index";
@import "./list/index";
@import "./content/index";
@import "./messages/index";
@import "./section/index";
@import "./errorlist/index";
@import "./fieldset/index";
@import "body/index";
@import "form/index";
@import "main/index";
@import "navbar/index";
@import "loading-indicator/index";
@import "modal/index";
@import "card/index";
@import "list/index";
@import "messages/index";
@import "section/index";
@import "errorlist/index";
@import "fieldset/index";
@import "sidebar/index";
@import "rules/index";
@import "category/index";
@import "post/index";
@import "post-block/index";
@import "post-message/index";
@import "posts/index";
@import "posts-header/index";
@import "posts-info/index";
@import "posts-section/index";

View file

@ -1,4 +1,7 @@
.main {
margin: 1% 10% 5% 10%;
background-color: $white;
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 0;
}

View file

@ -1,16 +1,32 @@
.messages {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
margin: 5px 0 20px 0;
padding: 15px 0;
color: $white;
background-color: $error-red;
&__item {
padding: 0 30px;
width: 80%;
padding: 20px 15px;
margin: 5px 0;
border-radius: 5px;
background-color: $focus-blue;
&--error {
background-color: $error-red;
}
&--warning {
background-color: $light-orange;
}
&--success {
background-color: $success-green;
}
}
}

View file

@ -1,12 +1,42 @@
.modal {
display: flex;
flex-direction: column;
align-items: center;
position: fixed;
width: 100%;
height: 100%;
top: 0;
background-color: $dark;
&__item {
display: flex;
flex-direction: column;
align-self: center;
margin: 20px 0;
padding: 20px;
width: 60%;
border-radius: 5px;
background-color: $white;
}
&__header {
padding: 5px 20px;
}
&__content {
padding: 10px 30px;
}
&__footer {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 10px;
}
}

View file

@ -0,0 +1,9 @@
.post-modal {
@extend .modal;
margin: 0;
padding: 0;
border-radius: 0;
cursor: pointer;
}

View file

@ -1 +1,3 @@
@import "modal";
@import "post-modal";

View file

@ -1,6 +1,7 @@
.post-block {
display: flex;
flex-direction: column;
align-items: center;
width: 70%;
margin: 0 0 2% 0;

View file

@ -15,6 +15,8 @@
background-color: $white;
cursor: initial;
&__header {
display: flex;
flex-direction: column;
@ -58,7 +60,11 @@
font-size: 18px;
& p {
padding: 20px 0 0 0;
padding: 10px 0;
}
& h1, h2, h3 {
margin: 20px 0 5px 0;
}
& img {

View file

@ -1,6 +1,7 @@
.posts-section {
display: flex;
flex-direction: column;
width: 95%;
margin: 20px;
padding: 10px;

View file

@ -1,4 +1,6 @@
.rules {
padding: 0;
&__item {
display: flex;
justify-content: space-between;
@ -25,4 +27,20 @@
background-color: darken($azureish-white, +10%);
}
}
&__info {
display: flex;
align-items: center;
width: 80%;
}
&__title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
& .badge {
display: flex;
}
}

View file

@ -0,0 +1,26 @@
.sidebar {
display: flex;
flex-direction: column;
align-items: center;
align-self: start;
position: sticky;
top: 5%;
width: 20%;
&__nav {
width: 100%;
max-height: 80vh;
overflow: auto;
list-style: none;
border-radius: 5px;
font-family: $sidebar-font;
&__item {
padding: 2px 10px 5px 10px;
}
}
}

View file

@ -1,4 +1,6 @@
.read-button {
@extend .button;
margin: 20px 0 0 0;
color: $white;

View file

@ -1 +1,2 @@
@import "button";
@import "_read-button";

View file

@ -7,3 +7,4 @@
@import "input/index";
@import "label/index";
@import "help-text/index";
@import "badge/index";

View file

@ -0,0 +1,5 @@
@import "partials/index";
@import "components/index";
@import "elements/index";
@import "pages/index";

View file

@ -1,8 +0,0 @@
// General imports
@import "../../partials/variables";
@import "../../components/index";
@import "../../elements/index";
// Page specific
@import "./components/index";
@import "./elements/index";

View file

@ -1,5 +0,0 @@
.card {
&__footer > *:last-child {
margin: 0 0 0 10px;
}
}

View file

@ -1 +0,0 @@
@import "card";

View file

@ -1,27 +0,0 @@
.category-modal {
display: flex;
flex-direction: column;
align-self: center;
margin: 20px 0;
width: 50%;
border-radius: 2px;
background-color: $white;
&__header {
padding: 5px 20px;
}
&__content {
padding: 10px 30px;
}
&__footer {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 10px;
}
}

View file

@ -1 +0,0 @@
@import "category-modal";

View file

@ -1,2 +0,0 @@
@import "card/index";
@import "category-modal/index";

View file

@ -1,8 +1,7 @@
// General imports
@import "../../partials/variables";
@import "../../components/index";
@import "../../elements/index";
// Page specific
@import "./components/index";
@import "./elements/index";
#categories--page {
& .card {
&__footer > *:last-child {
margin: 0 0 0 10px;
}
}
}

View file

@ -1 +0,0 @@
@import "category-form";

View file

@ -1 +0,0 @@
@import "category-form/index";

View file

@ -1,8 +1,2 @@
// General imports
@import "../../partials/variables";
@import "../../components/index";
@import "../../elements/index";
// Page specific
@import "./components/index";
@import "./elements/index";
#category--page {
}

Some files were not shown because too many files have changed in this diff Show more