diff --git a/package-lock.json b/package-lock.json
index 484ff31..34d1ce9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -339,6 +339,15 @@
"@babel/helper-plugin-utils": "^7.0.0"
}
},
+ "@babel/plugin-syntax-dynamic-import": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0.tgz",
+ "integrity": "sha512-Gt9xNyRrCHCiyX/ZxDGOcBnlJl0I3IWicpZRC4CdC0P5a/I07Ya2OAMEBU+J7GmRFVmIetqEYRko6QYRuKOESw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
"@babel/plugin-syntax-json-strings": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz",
@@ -1891,6 +1900,12 @@
"wrap-ansi": "^2.0.0"
}
},
+ "clone": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+ "dev": true
+ },
"clone-deep": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz",
@@ -2531,6 +2546,15 @@
"utila": "~0.4"
}
},
+ "dom-helpers": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
+ "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.1.2"
+ }
+ },
"dom-serializer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
@@ -3035,6 +3059,12 @@
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
},
+ "fast-diff": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
+ "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
+ "dev": true
+ },
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
@@ -3312,14 +3342,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3334,20 +3362,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -3464,8 +3489,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"ini": {
"version": "1.3.5",
@@ -3477,7 +3501,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -3492,7 +3515,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -3500,14 +3522,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -3526,7 +3546,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -3607,8 +3626,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -3620,7 +3638,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -3742,7 +3759,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -5967,6 +5983,12 @@
"no-case": "^2.2.0"
}
},
+ "parchment": {
+ "version": "1.1.4",
+ "resolved": "http://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
+ "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
+ "dev": true
+ },
"parse-asn1": {
"version": "5.1.1",
"resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
@@ -6373,6 +6395,39 @@
"integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==",
"dev": true
},
+ "quill": {
+ "version": "1.3.6",
+ "resolved": "http://registry.npmjs.org/quill/-/quill-1.3.6.tgz",
+ "integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==",
+ "dev": true,
+ "requires": {
+ "clone": "^2.1.1",
+ "deep-equal": "^1.0.1",
+ "eventemitter3": "^2.0.3",
+ "extend": "^3.0.1",
+ "parchment": "^1.1.4",
+ "quill-delta": "^3.6.2"
+ },
+ "dependencies": {
+ "eventemitter3": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
+ "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=",
+ "dev": true
+ }
+ }
+ },
+ "quill-delta": {
+ "version": "3.6.3",
+ "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
+ "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
+ "dev": true,
+ "requires": {
+ "deep-equal": "^1.0.1",
+ "extend": "^3.0.2",
+ "fast-diff": "1.1.2"
+ }
+ },
"randombytes": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz",
@@ -6498,6 +6553,27 @@
"prop-types": "^15.6.0"
}
},
+ "react-router-scroll-4": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/react-router-scroll-4/-/react-router-scroll-4-1.0.0-beta.2.tgz",
+ "integrity": "sha512-K67Dnm75naSBs/WYc2CDNxqU+eE8iA3I0wSCArgGSHb0xR/7AUcgUEXtCxrQYVTogXvjVK60gmwYvOyRQ6fuBA==",
+ "dev": true,
+ "requires": {
+ "scroll-behavior": "^0.9.1",
+ "warning": "^3.0.0"
+ },
+ "dependencies": {
+ "warning": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
+ "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
+ "dev": true,
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+ },
"react-svg-inline": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/react-svg-inline/-/react-svg-inline-2.1.1.tgz",
@@ -7064,6 +7140,16 @@
"ajv-keywords": "^3.1.0"
}
},
+ "scroll-behavior": {
+ "version": "0.9.9",
+ "resolved": "https://registry.npmjs.org/scroll-behavior/-/scroll-behavior-0.9.9.tgz",
+ "integrity": "sha1-6/4GWEVbgq2IW2YZUhVBZnTazOI=",
+ "dev": true,
+ "requires": {
+ "dom-helpers": "^3.2.1",
+ "invariant": "^2.2.2"
+ }
+ },
"scss-tokenizer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
diff --git a/package.json b/package.json
index 3db46c9..66cd57e 100644
--- a/package.json
+++ b/package.json
@@ -7,9 +7,13 @@
"type": "git",
"url": "git+https://github.com/igoradamenko/rtnews-ui.git"
},
- "sideEffects": ["*.scss", "./src/main.jsx"],
+ "sideEffects": [
+ "*.scss",
+ "./src/main.jsx"
+ ],
"devDependencies": {
"@babel/core": "^7.1.2",
+ "@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/polyfill": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-react": "^7.0.0",
@@ -25,9 +29,11 @@
"node-sass": "^4.10.0",
"preact": "^8.3.1",
"preact-compat": "^3.18.4",
+ "quill": "^1.3.6",
"react-redux": "^5.0.7",
"react-router-dom": "^4.3.1",
"react-router-hash-link": "^1.2.1",
+ "react-router-scroll-4": "^1.0.0-beta.2",
"react-svg-inline": "^2.1.1",
"redux": "^4.0.0",
"sass-loader": "^7.1.0",
diff --git a/src/api.js b/src/api.js
index 7a99fb9..c8a42f7 100644
--- a/src/api.js
+++ b/src/api.js
@@ -123,16 +123,27 @@ export async function pollActiveArticle(ms = 295) {
}
}
-export function addArticle(link, title = "", snippet = "") {
+/**
+ * Adds article or updates it if title already exists
+ */
+export function addArticle(link, title = "", snippet = "", content = "") {
const body = { link };
- if (title.length > 0) body.title = title;
- if (snippet.length > 0) body.snippet = snippet;
+ if (!title || title.length > 0) body.title = title;
+ if (!snippet || snippet.length > 0) body.snippet = snippet;
+ if (!content || content.length > 0) body.content = content;
const headers = new Headers();
headers.append("Content-Type", "application/json");
const url = title.length > 0 ? "/news/manual" : "/news";
+ for (let [slug, article] of articlesCache.entries()) {
+ if (article.title === title) {
+ articlesCache.delete(slug);
+ articlesIdSlugMap.delete(article.id);
+ }
+ }
+
return request(url, {
method: "POST",
body: JSON.stringify(body),
@@ -140,6 +151,24 @@ export function addArticle(link, title = "", snippet = "") {
});
}
+export function updateArticle(updated) {
+ for (let [slug, article] of articlesCache.entries()) {
+ if (article.id === updated.id) {
+ articlesCache.delete(slug);
+ articlesIdSlugMap.delete(article.id);
+ }
+ }
+
+ const headers = new Headers();
+ headers.append("Content-Type", "application/json");
+
+ return request("/news/manual", {
+ method: "POST",
+ body: JSON.stringify(updated),
+ headers,
+ });
+}
+
export function moveArticle(id, offset) {
return request(`/news/moveid/${id}/${offset}`, { method: "PUT" });
}
diff --git a/src/article.jsx b/src/article.jsx
index 22532cb..ee1e5d7 100644
--- a/src/article.jsx
+++ b/src/article.jsx
@@ -1,7 +1,8 @@
import { createElement, PureComponent } from "react";
-import { formatDate, scrollIntoView } from "./utils.js";
-import { getArticle } from "./api.js";
+import { formatDate, scrollIntoView, waitFor } from "./utils.js";
+import { getArticle, updateArticle } from "./api.js";
+import articleCache from "./articleCache";
import { remark } from "./settings.js";
import Remark from "./remark.jsx";
@@ -10,98 +11,289 @@ import SVGInline from "react-svg-inline";
import GearIcon from "./static/svg/gear.svg";
import NotFound from "./notFound.jsx";
import Error from "./error.jsx";
+import RichEditor from "./richEditor.jsx";
+import { addNotification, removeNotification } from "./store.jsx";
-export default class Article extends PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- article: null,
- error: null,
- };
- }
- componentDidMount() {
- getArticle(this.props.slug)
- .then(article => {
- document.title = article.title + "| Новости Радио-Т";
- this.setState({ article });
- })
- .catch(error => {
- this.setState({ error });
- });
+function ArticleFactory(editable = false) {
+ return class Article extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ article: null,
+ error: null,
+ previewSnippet: null,
+ previewContent: null,
+ /**
+ * view|edit|preview
+ */
+ mode: "view",
+ };
+ }
+ componentDidMount() {
+ getArticle(this.props.slug)
+ .then(article => {
+ document.title = article.title + "| Новости Радио-Т";
+ this.setState({ article });
+ })
+ .catch(error => {
+ this.setState({ error });
+ });
- setTimeout(() => {
- const hash = window.location.hash;
- if (hash === "") return;
- const el = document.getElementById(hash.substr(1));
- if (el) {
- scrollIntoView(el);
+ setTimeout(() => {
+ const hash = window.location.hash;
+ if (hash === "") return;
+ const el = document.getElementById(hash.substr(1));
+ if (el) {
+ scrollIntoView(el);
+ }
+ }, 200);
+ }
+ async edit() {
+ this.setState({
+ previewSnippet: this.state.article.snippet || "",
+ previewContent: this.state.article.content || "",
+ mode: "edit",
+ });
+ await waitFor(() => this.snippeteditor, 10000);
+ this.snippeteditor.focus();
+ }
+ cancelEdit() {
+ this.setState({
+ previewContent: null,
+ previewSnippet: null,
+ mode: "view",
+ });
+ }
+ preview() {
+ this.state.previewSnippet = this.snippeteditor.getContent();
+ this.state.previewContent = this.editor.getContent();
+ this.setState({ mode: "preview" });
+ }
+ async save() {
+ let notification;
+ try {
+ const snippet = this.snippeteditor
+ ? this.snippeteditor.getContent()
+ : this.state.previewSnippet;
+ const content = this.editor
+ ? this.editor.getContent()
+ : this.state.previewContent;
+ notification = addNotification({
+ data: "Сохраняю новость",
+ time: null,
+ });
+ await updateArticle({ ...this.state.article, content, snippet });
+ articleCache.invalidate();
+ this.setState({
+ previewContent: null,
+ previewSnippet: null,
+ article: { ...this.state.article, snippet, content },
+ mode: "view",
+ });
+ removeNotification(notification);
+ addNotification({
+ data: "Новость сохранена",
+ time: 3000,
+ });
+ } catch (e) {
+ console.error(e);
+ removeNotification(notification);
+ addNotification({
+ data: (
+
+ Ошибка при сохранении,{" "}
+ {
+ window.location.reload;
+ }}
+ >
+ обновить страницу?
+
+
+ ),
+ time: 10000,
+ });
}
- }, 200);
- }
- render() {
- if (
- this.state.error &&
- this.state.error.status &&
- this.state.error.status === 404
- )
- return ;
- if (this.state.error)
+ }
+ render() {
+ if (
+ this.state.error &&
+ this.state.error.status &&
+ this.state.error.status === 404
+ )
+ return ;
+ if (this.state.error)
+ return (
+
+ );
+ if (this.state.article === null) return ;
return (
-
- );
- if (this.state.article === null) return ;
- return (
-
-
- {this.state.article.geek && (
-
+
+
+ {editable && [
+ this.state.mode === "view" && (
+
+ this.edit()}
+ >
+ Редактировать
+
+
+ ),
+ this.state.mode === "edit" && (
+
+ this.cancelEdit()}
+ >
+ Отменить
+
+ {" / "}
+ this.preview()}
+ >
+ Превью
+
+ {" / "}
+ this.save()}
+ >
+ Сохранить
+
+
+ ),
+ this.state.mode === "preview" && (
+
+ this.cancelEdit()}
+ >
+ Отменить
+
+ {" / "}
+ this.setState({ mode: "edit" })}
+ >
+ Продолжить
+
+ {" / "}
+ this.save()}
+ >
+ Сохранить
+
+
+ ),
+ ]}
+ {!editable && (
+
+ )}
+ {editable && this.state.mode === "preview" && (
+
)}
-
- {this.state.article.title}
-
-
-
-
- {this.state.article.domain}
-
-
+ )}
+ {this.state.mode === "edit" && [
+
+ Сниппет
+
,
+
(this.snippeteditor = ref)}
+ placeholder="сниппет"
+ rich={false}
+ />,
+
+ Контент
+
,
+ (this.editor = ref)}
+ placeholder="контент"
+ rich={true}
+ />,
+ ]}
+
+
-
-
-
-
-
- );
- }
+
+ );
+ }
+ };
}
+
+export const Article = ArticleFactory(false);
+export const EditableArticle = ArticleFactory(true);
diff --git a/src/index.html b/src/index.html
index edac3d6..6accca2 100644
--- a/src/index.html
+++ b/src/index.html
@@ -6,6 +6,8 @@
+
+
Новости для Радио-Т
diff --git a/src/main.jsx b/src/main.jsx
index 412b154..609d726 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -25,6 +25,7 @@ import { waitDOMReady, sleep, scrollIntoView } from "./utils.js";
import Head from "./head.jsx";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
+import { ScrollContext } from "react-router-scroll-4";
import { Provider, connect } from "react-redux";
import AddArticle from "./add.jsx";
import {
@@ -33,7 +34,7 @@ import {
DeletedListing,
Sorter,
} from "./articleListings.jsx";
-import Article from "./article.jsx";
+import { Article, EditableArticle } from "./article.jsx";
import Feeds from "./feeds.jsx";
import LoginForm from "./login.jsx";
import NotFound from "./notFound.jsx";
@@ -54,21 +55,31 @@ class App extends Component {
path="/"
exact={true}
render={() => (
- (window[listingRef] = ref)}
- />
+
+ (window[listingRef] = ref)}
+ />
+
)}
/>
}
+ render={() => (
+
+
+
+ )}
/>
}
+ render={() => (
+
+
+
+ )}
/>
} />
}
+ render={props =>
+ this.props.isAdmin ? (
+
+ ) : (
+
+ )
+ }
/>
} />
diff --git a/src/quill-overloads.css b/src/quill-overloads.css
new file mode 100644
index 0000000..6d47651
--- /dev/null
+++ b/src/quill-overloads.css
@@ -0,0 +1,34 @@
+/**
+ * everything is important here, it's
+ * a serious business
+ */
+
+.ql-container {
+ min-height: 6rem !important;
+ height: auto !important;
+ max-height: calc(100vh - 6rem) !important;
+ font: inherit !important;
+}
+
+.ql-editor {
+ max-height: calc(100vh - 6rem) !important;
+ height: auto !important;
+}
+
+.ql-editor a {
+ text-decoration: none !important;
+}
+
+/* night theme overloads */
+
+html[data-theme="night"] .ql-fill {
+ fill: #ccc !important;
+}
+
+html[data-theme="night"] .ql-stroke {
+ stroke: #ccc !important;
+}
+
+html[data-theme="night"] .ql-picker-label {
+ color: #ccc !important;
+}
diff --git a/src/richEditor.jsx b/src/richEditor.jsx
new file mode 100644
index 0000000..eb1747b
--- /dev/null
+++ b/src/richEditor.jsx
@@ -0,0 +1,105 @@
+import { createElement, PureComponent } from "react";
+import { waitFor } from "./utils";
+
+let Quill = null;
+
+export default class RichEditor extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loaded: false,
+ };
+ if (Quill !== null) {
+ this.setState({ loaded: true });
+ return;
+ }
+
+ // prettier-ignore
+ Promise.all([
+ import(
+ /* webpackChunkName: "quill" */
+ /* webpackMode: "lazy" */
+ "quill"
+ ),
+ import(
+ /* webpackChunkName: "quill" */
+ /* webpackMode: "lazy" */
+ "quill/dist/quill.core.css"
+ ),
+ import(
+ /* webpackChunkName: "quill" */
+ /* webpackMode: "lazy" */
+ "quill/dist/quill.snow.css"
+ ),
+ import(
+ /* webpackChunkName: "quill" */
+ /* webpackMode: "lazy" */
+ "./quill-overloads.css"
+ ),
+ ]).then(([ImportedQuill]) => {
+ this.setState({loaded: true})
+ Quill = ImportedQuill.default;
+ });
+ }
+ componentDidMount(...args) {
+ super.componentDidMount && super.componentDidMount(...args);
+
+ waitFor(() => Quill !== null).then(() => {
+ if (this.props.rich) {
+ this.quill = new Quill(this.editor, {
+ theme: "snow",
+ placeholder: this.props.placeholder || "",
+ modules: {
+ toolbar: [
+ [{ header: [1, 2, 3, 4, 5, 6, false] }],
+ ["bold", "italic", "underline", "strike"],
+ [{ align: [] }],
+ [{ list: "ordered" }, { list: "bullet" }],
+ [{ script: "sub" }, { script: "super" }],
+ ["clean"],
+ ],
+ },
+ });
+ } else {
+ this.quill = new Quill(this.editor, {
+ theme: "snow",
+ placeholder: this.props.placeholder || "",
+ modules: {
+ toolbar: null,
+ },
+ formats: [],
+ });
+ }
+ });
+ }
+ getContent() {
+ if (!this.props.rich)
+ return this.quill.root.innerHTML
+ .replace(/(
|<\/p>)/gi, " ")
+ .replace(/(<([^>]+)>)/gi, "");
+ return this.quill.root.innerHTML;
+ }
+ focus() {
+ setTimeout(() => {
+ this.quill.root.focus();
+ }, 100);
+ }
+ render() {
+ if (!this.state.loaded) return "Загружаю";
+ return (
+
+
(this.editor = ref)}
+ dangerouslySetInnerHTML={{ __html: this.props.content }}
+ />
+
+ );
+ }
+}
diff --git a/src/style.scss b/src/style.scss
index 612a531..ebfb716 100644
--- a/src/style.scss
+++ b/src/style.scss
@@ -290,7 +290,8 @@ p {
@media screen and (max-width: 40rem) {
&__item-header {
- word-break: break-all;
+ -webkit-hyphens: auto;
+ hyphens: auto;
}
}
@@ -943,7 +944,8 @@ p {
@media screen and (max-width: 40rem) {
&__title {
- word-break: break-all;
+ -webkit-hyphens: auto;
+ hyphens: auto;
}
}
@@ -1072,9 +1074,10 @@ p {
}
}
-/* full-post */
+/* article */
+/* denotes full post on /post/* route */
-.full-post {
+.article {
&__title {
font-size: 2em;
margin-bottom: 0.5em;
@@ -1091,6 +1094,38 @@ p {
&__comments {
margin-top: 2rem;
}
+
+ &__edit-button-edit {
+ opacity: 0.6;
+ }
+
+ &__editor-title-snippet {
+ margin-top: 1rem;
+ }
+
+ &__edit {
+ margin-top: -1rem;
+ margin-bottom: 1rem;
+ text-align: right;
+ font-size: 0.8em;
+ }
+
+ &__snippet {
+ background: hsla(40, 60%, 50%, 0.06);
+ border-radius: 0.2rem;
+ padding: 1rem 0.5rem 0.5rem 0.5rem;
+ width: 100%;
+ margin-left: -0.5rem;
+ margin-bottom: 0.5rem;
+ }
+
+ &__snippet::before {
+ content: "Сниппет:";
+ display: block;
+ opacity: 0.6;
+ position: relative;
+ top: -0.7rem;
+ }
}
/* article-content */
@@ -1119,6 +1154,27 @@ p {
figure {
margin: 0;
}
+
+ blockquote {
+ font-style: italic;
+ margin: 0;
+ opacity: 0.8;
+ }
+
+ .ql-align-center {
+ text-align: center;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ .ql-align-right {
+ text-align: right;
+ }
+
+ .ql-align-left {
+ text-align: left;
+ }
}
/* drop */
@@ -1314,13 +1370,17 @@ html[data-theme="night"] {
background: rgba(255, 255, 255, 0.26);
}
- /* full-post */
+ /* article */
- .full-post__comments {
+ .article__comments {
background: #fff;
padding: 1rem;
}
+ .article__snippet {
+ background: hsla(40, 60%, 90%, 0.2);
+ }
+
/* drop */
.drop-top {
border-top-color: hsla(200, 60%, 70%, 1);
diff --git a/webpack.config.js b/webpack.config.js
index e6788da..b83b1b7 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -12,6 +12,8 @@ module.exports = (a, args) => {
context: path.resolve(__dirname, "src"),
output: {
path: path.resolve(__dirname, "public"),
+ filename: "[name].js",
+ chunkFilename: "[name].component.js",
publicPath: "/",
},
mode: args.mode || "production",
@@ -23,6 +25,7 @@ module.exports = (a, args) => {
use: {
loader: "babel-loader",
options: {
+ plugins: ["@babel/plugin-syntax-dynamic-import"],
presets: [
[
"@babel/preset-env",
@@ -41,6 +44,7 @@ module.exports = (a, args) => {
use: {
loader: "babel-loader",
options: {
+ plugins: ["@babel/plugin-syntax-dynamic-import"],
presets: [
[
"@babel/preset-env",
@@ -60,6 +64,15 @@ module.exports = (a, args) => {
},
},
},
+ {
+ test: /\.css$/,
+ use: [
+ {
+ loader: MiniCssExtractPlugin.loader,
+ },
+ "css-loader",
+ ],
+ },
{
test: /\.scss$/,
use: [
@@ -131,7 +144,7 @@ module.exports = (a, args) => {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
enforce: true,
- chunks: "all",
+ chunks: "initial",
},
},
},