editor very WIP

This commit is contained in:
bniwredyc 2023-03-08 17:35:13 +01:00
parent a7b0df4b24
commit 195781741c
21 changed files with 7469 additions and 1107 deletions

8047
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -33,19 +33,19 @@
"@aws-sdk/client-s3": "^3.282.0", "@aws-sdk/client-s3": "^3.282.0",
"@aws-sdk/lib-storage": "^3.282.0", "@aws-sdk/lib-storage": "^3.282.0",
"formidable": "^2.1.1", "formidable": "^2.1.1",
"i18next": "^22.4.10", "i18next": "^22.4.11",
"mailgun.js": "^8.2.0" "mailgun.js": "^8.2.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.0", "@babel/core": "^7.21.0",
"@graphql-codegen/cli": "^3.2.1", "@graphql-codegen/cli": "^3.2.2",
"@graphql-codegen/typescript": "^3.0.1", "@graphql-codegen/typescript": "^3.0.2",
"@graphql-codegen/typescript-operations": "^3.0.1", "@graphql-codegen/typescript-operations": "^3.0.2",
"@graphql-codegen/typescript-urql": "^3.7.3", "@graphql-codegen/typescript-urql": "^3.7.3",
"@graphql-codegen/urql-introspection": "^2.2.1", "@graphql-codegen/urql-introspection": "^2.2.1",
"@graphql-tools/url-loader": "^7.17.13", "@graphql-tools/url-loader": "^7.17.14",
"@graphql-typed-document-node/core": "^3.1.2", "@graphql-typed-document-node/core": "^3.1.2",
"@nanostores/router": "^0.8.1", "@nanostores/router": "^0.8.2",
"@nanostores/solid": "^0.3.2", "@nanostores/solid": "^0.3.2",
"@popperjs/core": "^2.11.6", "@popperjs/core": "^2.11.6",
"@solid-primitives/memo": "^1.2.0", "@solid-primitives/memo": "^1.2.0",
@ -53,15 +53,44 @@
"@solid-primitives/storage": "^1.3.7", "@solid-primitives/storage": "^1.3.7",
"@solid-primitives/upload": "^0.0.109", "@solid-primitives/upload": "^0.0.109",
"@solidjs/meta": "^0.28.2", "@solidjs/meta": "^0.28.2",
"@tiptap/core": "^2.0.0-beta.220",
"@tiptap/extension-blockquote": "^2.0.0-beta.220",
"@tiptap/extension-bold": "^2.0.0-beta.220",
"@tiptap/extension-bubble-menu": "^2.0.0-beta.220",
"@tiptap/extension-bullet-list": "^2.0.0-beta.220",
"@tiptap/extension-character-count": "^2.0.0-beta.220",
"@tiptap/extension-collaboration": "^2.0.0-beta.220",
"@tiptap/extension-collaboration-cursor": "^2.0.0-beta.220",
"@tiptap/extension-document": "^2.0.0-beta.220",
"@tiptap/extension-dropcursor": "^2.0.0-beta.220",
"@tiptap/extension-floating-menu": "^2.0.0-beta.220",
"@tiptap/extension-focus": "^2.0.0-beta.220",
"@tiptap/extension-gapcursor": "^2.0.0-beta.220",
"@tiptap/extension-hard-break": "^2.0.0-beta.220",
"@tiptap/extension-heading": "^2.0.0-beta.220",
"@tiptap/extension-highlight": "^2.0.0-beta.220",
"@tiptap/extension-history": "^2.0.0-beta.220",
"@tiptap/extension-horizontal-rule": "^2.0.0-beta.220",
"@tiptap/extension-image": "^2.0.0-beta.220",
"@tiptap/extension-italic": "^2.0.0-beta.220",
"@tiptap/extension-link": "^2.0.0-beta.220",
"@tiptap/extension-list-item": "^2.0.0-beta.220",
"@tiptap/extension-ordered-list": "^2.0.0-beta.220",
"@tiptap/extension-paragraph": "^2.0.0-beta.220",
"@tiptap/extension-placeholder": "^2.0.0-beta.220",
"@tiptap/extension-strike": "^2.0.0-beta.220",
"@tiptap/extension-text": "^2.0.0-beta.220",
"@tiptap/extension-underline": "^2.0.0-beta.220",
"@tiptap/extension-youtube": "^2.0.0-beta.220",
"@types/express": "^4.17.15", "@types/express": "^4.17.15",
"@types/node": "^18.14.6", "@types/node": "^18.14.6",
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/eslint-plugin": "^5.54.1",
"@typescript-eslint/parser": "^5.54.0", "@typescript-eslint/parser": "^5.54.1",
"@urql/core": "^3.1.1", "@urql/core": "^3.1.1",
"@urql/devtools": "^2.0.3", "@urql/devtools": "^2.0.3",
"@urql/exchange-graphcache": "^5.0.9", "@urql/exchange-graphcache": "^5.0.9",
"babel-preset-solid": "^1.5.6", "babel-preset-solid": "^1.6.12",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"clsx": "^1.2.1", "clsx": "^1.2.1",
@ -75,18 +104,19 @@
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-solid": "^0.10.0", "eslint-plugin-solid": "^0.11.0",
"eslint-plugin-sonarjs": "^0.18.0", "eslint-plugin-sonarjs": "^0.18.0",
"eslint-plugin-unicorn": "^46.0.0", "eslint-plugin-unicorn": "^46.0.0",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
"graphql-ws": "^5.11.2", "graphql-ws": "^5.12.0",
"hast-util-select": "^5.0.4", "hast-util-select": "^5.0.4",
"husky": "^8.0.3", "husky": "^8.0.3",
"hygen": "^6.2.11", "hygen": "^6.2.11",
"i18next-http-backend": "^2.1.1", "i18next-http-backend": "^2.1.1",
"idb": "^7.1.1", "idb": "^7.1.1",
"jest": "^29.4.3", "install": "^0.13.0",
"jest": "^29.5.0",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lint-staged": "^13.1.2", "lint-staged": "^13.1.2",
"loglevel": "^1.8.1", "loglevel": "^1.8.1",
@ -97,6 +127,7 @@
"markdown-it-mark": "^3.0.1", "markdown-it-mark": "^3.0.1",
"markdown-it-replace-link": "^1.1.0", "markdown-it-replace-link": "^1.1.0",
"nanostores": "^0.7.4", "nanostores": "^0.7.4",
"npm": "^9.6.0",
"orderedmap": "^2.1.0", "orderedmap": "^2.1.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-eslint": "^15.0.1", "prettier-eslint": "^15.0.1",
@ -116,14 +147,15 @@
"rollup": "^3.18.0", "rollup": "^3.18.0",
"rollup-plugin-visualizer": "^5.9.0", "rollup-plugin-visualizer": "^5.9.0",
"sass": "^1.58.3", "sass": "^1.58.3",
"solid-js": "^1.6.11", "solid-js": "^1.6.12",
"solid-tiptap": "^0.5.1",
"solid-transition-group": "^0.0.13", "solid-transition-group": "^0.0.13",
"sort-package-json": "^2.3.0", "sort-package-json": "^2.3.0",
"stylelint": "^15.2.0", "stylelint": "^15.2.0",
"stylelint-config-css-modules": "^4.1.0", "stylelint-config-css-modules": "^4.1.0",
"stylelint-config-prettier-scss": "^0.0.1", "stylelint-config-prettier-scss": "^0.0.1",
"stylelint-config-standard-scss": "^7.0.1", "stylelint-config-standard-scss": "^7.0.1",
"stylelint-order": "^6.0.1", "stylelint-order": "^6.0.3",
"stylelint-scss": "^4.4.0", "stylelint-scss": "^4.4.0",
"swiper": "^8.4.7", "swiper": "^8.4.7",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
@ -134,10 +166,11 @@
"vite": "^4.1.4", "vite": "^4.1.4",
"vite-plugin-sass-dts": "^1.2.16", "vite-plugin-sass-dts": "^1.2.16",
"vite-plugin-solid": "^2.6.1", "vite-plugin-solid": "^2.6.1",
"vite-plugin-ssr": "^0.4.90", "vite-plugin-ssr": "^0.4.91",
"wonka": "^6.2.3", "wonka": "^6.2.3",
"ws": "^8.12.1", "ws": "^8.12.1",
"y-prosemirror": "^1.2.0", "y-indexeddb": "^9.0.9",
"y-prosemirror": "^1.0.20",
"y-protocols": "^1.0.5", "y-protocols": "^1.0.5",
"y-webrtc": "^10.2.4", "y-webrtc": "^10.2.4",
"yjs": "^13.5.48" "yjs": "^13.5.48"

View File

@ -231,5 +231,6 @@
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Your name will appear on your profile page and as your signature in publications, comments and responses", "Your name will appear on your profile page and as your signature in publications, comments and responses.": "Your name will appear on your profile page and as your signature in publications, comments and responses",
"zine": "zine", "zine": "zine",
"By time": "By time", "By time": "By time",
"New only": "New only" "New only": "New only",
"Short opening": "Short opening"
} }

View File

@ -249,5 +249,6 @@
"view": "просмотр", "view": "просмотр",
"zine": "журнал", "zine": "журнал",
"By time": "По порядку", "By time": "По порядку",
"New only": "Только новые" "New only": "Только новые",
"Short opening": "Небольшое вступление, чтобы заинтересовать читателя"
} }

View File

@ -1,6 +1,6 @@
import { Show, createMemo, createSignal, onMount, For } from 'solid-js' import { Show, createMemo, createSignal, onMount, For } from 'solid-js'
import { Comment } from './Comment' import { Comment } from './Comment'
import styles from '../../styles/Article.module.scss' import styles from './Article.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { Author, Reaction, ReactionKind } from '../../graphql/types.gen' import { Author, Reaction, ReactionKind } from '../../graphql/types.gen'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
@ -55,6 +55,8 @@ export const CommentsTree = (props: Props) => {
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT') Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT')
) )
console.log(JSON.parse(JSON.stringify(reactionEntities)))
const sortedComments = createMemo(() => { const sortedComments = createMemo(() => {
let newSortedComments = [...comments()] let newSortedComments = [...comments()]
newSortedComments = newSortedComments.sort(byCreated) newSortedComments = newSortedComments.sort(byCreated)

View File

@ -1,163 +0,0 @@
h1 {
@include font-size(4rem);
line-height: 1.1;
margin-top: 0.5em;
}
h2 {
line-height: 1.1;
}
img {
max-width: 100%;
}
.article {
padding-top: 2em;
}
.article__header {
margin-bottom: 2em;
@include media-breakpoint-up(md) {
margin: 0 -16.6666% 2em;
}
}
.article__cover {
background-size: cover;
height: 0;
padding-bottom: 56.2%;
}
.article__body {
font-size: 1.7rem;
line-height: 1.6;
img {
display: block;
margin-bottom: 0.5em;
}
blockquote {
border-left: 4px solid;
font-size: 2rem;
font-weight: 500;
font-style: italic;
line-height: 1.4;
margin: 1.5em 0 1.5em -16.6666%;
padding: 0 0 0 1em;
}
mark {
background: none;
font-size: 2rem;
font-weight: bold;
line-height: 1.4;
}
}
.article__author {
margin-bottom: 2em;
}
.article__authors-list {
margin-top: 2em;
h4 {
color: #696969;
font-size: 1.5rem;
font-weight: normal;
}
}
.write-comment {
border: 2px solid #f6f6f6;
@include font-size(1.7rem);
outline: none;
padding: 0.2em 0.4em;
width: 100%;
&::placeholder {
color: #858585;
}
}
.comment-warning {
background: #f6f6f6;
@include font-size(2.2rem);
margin-bottom: 1em;
padding: 2.4rem 1.8rem;
}
.article-stats {
border-bottom: 1px solid #e8e8e8;
border-top: 4px solid #000;
padding: 3.2rem 0;
}
.article-stats__item {
@include font-size(1.7rem);
font-weight: 500;
display: inline-block;
margin-right: $grid-gutter-width;
vertical-align: baseline;
.icon {
display: inline-block;
margin-right: 0.2em;
transition: filter 0.2s;
vertical-align: middle;
}
img {
display: block;
}
a {
border: none;
&:hover {
.icon {
filter: invert(1);
}
}
}
}
.article-stats__item--likes {
.icon {
vertical-align: baseline;
}
.icon:last-of-type {
// transform: rotate(180deg);
transform-origin: center;
margin-left: 0.3em;
vertical-align: middle;
}
}
.topics-list {
margin: 2.4rem 0;
.article__topic {
display: inline-block;
margin: 0 0.8rem 0.8rem 0;
a {
background: #f6f6f6;
color: #000;
padding: 0.4rem 0.8rem;
transition: background-color 0.2s;
&:hover {
background-color: rgb(0 0 0 / 20%);
}
}
}
}

View File

@ -1,5 +1,4 @@
import { capitalize, formatDate } from '../../utils' import { capitalize, formatDate } from '../../utils'
import './Full.scss'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { AuthorCard } from '../Author/Card' import { AuthorCard } from '../Author/Card'
import { createMemo, createSignal, For, Match, onMount, Show, Switch } from 'solid-js' import { createMemo, createSignal, For, Match, onMount, Show, Switch } from 'solid-js'
@ -7,8 +6,6 @@ import type { Author, Shout } from '../../graphql/types.gen'
import MD from './MD' import MD from './MD'
import { SharePopup } from './SharePopup' import { SharePopup } from './SharePopup'
import { getDescription } from '../../utils/meta' import { getDescription } from '../../utils/meta'
import stylesHeader from '../Nav/Header.module.scss'
import styles from '../../styles/Article.module.scss'
import { ShoutRatingControl } from './ShoutRatingControl' import { ShoutRatingControl } from './ShoutRatingControl'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { CommentsTree } from './CommentsTree' import { CommentsTree } from './CommentsTree'
@ -20,6 +17,8 @@ import { router } from '../../stores/router'
import { useReactions } from '../../context/reactions' import { useReactions } from '../../context/reactions'
import { Title } from '@solidjs/meta' import { Title } from '@solidjs/meta'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import stylesHeader from '../Nav/Header.module.scss'
import styles from './Article.module.scss'
interface ArticleProps { interface ArticleProps {
article: Shout article: Shout
@ -108,7 +107,7 @@ export const FullArticle = (props: ArticleProps) => {
return ( return (
<> <>
<Title>{props.article.title}</Title> <Title>{props.article.title}</Title>
<div class="shout wide-container"> <div class="wide-container">
<article class="col-md-6 shift-content"> <article class="col-md-6 shift-content">
<div class={styles.shoutHeader}> <div class={styles.shoutHeader}>
<div class={styles.shoutTopic}> <div class={styles.shoutTopic}>
@ -168,7 +167,7 @@ export const FullArticle = (props: ArticleProps) => {
<Show when={media() && props.article.layout === 'image'}> <Show when={media() && props.article.layout === 'image'}>
<Slider slidesPerView={1} isPageGallery={true} isCardsWithCover={true} hasThumbs={true}> <Slider slidesPerView={1} isPageGallery={true} isCardsWithCover={true} hasThumbs={true}>
<For each={media() || []}> <For each={media() || []}>
{(m: MediaItem) => ( {(m) => (
<div class="swiper-slide"> <div class="swiper-slide">
<div class="swiper-slide__inner"> <div class="swiper-slide__inner">
<img src={m.url || m.pic} alt={m.title} loading="lazy" /> <img src={m.url || m.pic} alt={m.title} loading="lazy" />
@ -181,7 +180,7 @@ export const FullArticle = (props: ArticleProps) => {
</Slider> </Slider>
</Show> </Show>
<div class="shout wide-container"> <div class=" wide-container">
<div class="col-md-8 shift-content"> <div class="col-md-8 shift-content">
<div class={styles.shoutStats}> <div class={styles.shoutStats}>
<div class={styles.shoutStatsItem}> <div class={styles.shoutStatsItem}>

View File

@ -35,7 +35,10 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
const shoutRatingReactions = createMemo(() => const shoutRatingReactions = createMemo(() =>
Object.values(reactionEntities).filter( Object.values(reactionEntities).filter(
(r) => [ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) && r.shout.id === props.shout.id (r) =>
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
r.shout.id === props.shout.id &&
!r.replyTo
) )
) )

View File

@ -0,0 +1,7 @@
.container {
width: 680px;
}
.editor {
width: 100%;
}

View File

@ -0,0 +1,132 @@
import { createTiptapEditor } from 'solid-tiptap'
import { clsx } from 'clsx'
import { useLocalize } from '../../context/localize'
import { Blockquote } from '@tiptap/extension-blockquote'
import { Bold } from '@tiptap/extension-bold'
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
import * as Y from 'yjs'
import { WebrtcProvider } from 'y-webrtc'
import { Dropcursor } from '@tiptap/extension-dropcursor'
import { Italic } from '@tiptap/extension-italic'
import { Strike } from '@tiptap/extension-strike'
import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
import { Underline } from '@tiptap/extension-underline'
import { FloatingMenu } from '@tiptap/extension-floating-menu'
import { BulletList } from '@tiptap/extension-bullet-list'
import { OrderedList } from '@tiptap/extension-ordered-list'
import { ListItem } from '@tiptap/extension-list-item'
import { CharacterCount } from '@tiptap/extension-character-count'
import { Collaboration } from '@tiptap/extension-collaboration'
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
import { Placeholder } from '@tiptap/extension-placeholder'
import { Gapcursor } from '@tiptap/extension-gapcursor'
import { HardBreak } from '@tiptap/extension-hard-break'
import { Heading } from '@tiptap/extension-heading'
import { Highlight } from '@tiptap/extension-highlight'
import { Link } from '@tiptap/extension-link'
import { Youtube } from '@tiptap/extension-youtube'
import { Document } from '@tiptap/extension-document'
import { Text } from '@tiptap/extension-text'
import { Image } from '@tiptap/extension-image'
import { History } from '@tiptap/extension-history'
import { Paragraph } from '@tiptap/extension-paragraph'
import Focus from '@tiptap/extension-focus'
import { TrailingNode } from './extensions/TrailingNode'
import './Prosemirror.scss'
import styles from './Editor.module.scss'
import { Show } from 'solid-js'
import { EditorBubbleMenu } from './EditorBubbleMenu'
import { EditorFloatingMenu } from './EditorFloatingMenu'
type EditorProps = {
initialContent?: string
}
// const ydoc = new Y.Doc()
// // TODO
// const provider = new WebrtcProvider('slug!!!!!!', ydoc)
export const Editor = (props: EditorProps) => {
const { t } = useLocalize()
const editorElRef: {
current: HTMLDivElement
} = {
current: null
}
const bubbleMenuRef: {
current: HTMLDivElement
} = {
current: null
}
const floatingMenuRef: {
current: HTMLDivElement
} = {
current: null
}
const editor = createTiptapEditor(() => ({
element: editorElRef.current,
extensions: [
Document,
Text,
Paragraph,
Dropcursor,
Blockquote,
Bold,
Italic,
Strike,
HorizontalRule,
Underline,
BubbleMenu.configure({
element: bubbleMenuRef.current
}),
FloatingMenu.configure({
tippyOptions: {
placement: 'left'
},
element: floatingMenuRef.current
}),
BulletList,
OrderedList,
ListItem,
CharacterCount,
// Collaboration.configure({
// document: ydoc
// }),
// CollaborationCursor.configure({
// provider,
// user: {
// name: 'Cyndi Lauper',
// color: '#f783ac'
// }
// }),
// TODO conditional indexedDB
// History,
Placeholder.configure({
placeholder: t('Short opening')
}),
Focus,
Gapcursor,
HardBreak,
Heading,
Highlight,
Image,
Link,
Youtube,
TrailingNode
]
}))
return (
<div class={clsx('container', styles.container)}>
<div class={styles.editor} ref={(el) => (editorElRef.current = el)} />
<EditorBubbleMenu editor={editor()} ref={(el) => (bubbleMenuRef.current = el)} />
<EditorFloatingMenu editor={editor()} ref={(el) => (floatingMenuRef.current = el)} />
</div>
)
}
export default Editor

View File

@ -0,0 +1,14 @@
import type { Editor } from '@tiptap/core'
type BubbleMenuProps = {
editor: Editor
ref: (el: HTMLDivElement) => void
}
export const EditorBubbleMenu = (props: BubbleMenuProps) => {
return (
<div ref={props.ref}>
<button>bold</button>
</div>
)
}

View File

@ -0,0 +1,4 @@
.editorFloatingMenu {
position: relative;
left: -100%;
}

View File

@ -0,0 +1,15 @@
import type { Editor } from '@tiptap/core'
import styles from './EditorFloatingMenu.module.scss'
type FloatingMenuProps = {
editor: Editor
ref: (el: HTMLDivElement) => void
}
export const EditorFloatingMenu = (props: FloatingMenuProps) => {
return (
<div ref={props.ref} class={styles.editorFloatingMenu}>
<button>+</button>
</div>
)
}

View File

@ -0,0 +1,14 @@
.ProseMirror {
outline: none;
}
.ProseMirror p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
font-weight: 500;
font-size: 20px;
line-height: 30px;
opacity: 0.3;
}

View File

@ -0,0 +1,69 @@
import { Extension } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'
function nodeEqualsType({ types, node }) {
return (Array.isArray(types) && types.includes(node.type)) || node.type === types
}
/**
* Extension based on:
* - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js
* - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts
*/
export interface TrailingNodeOptions {
node: string
notAfter: string[]
}
export const TrailingNode = Extension.create<TrailingNodeOptions>({
name: 'trailingNode',
addOptions() {
return {
node: 'paragraph',
notAfter: ['paragraph']
}
},
addProseMirrorPlugins() {
const plugin = new PluginKey(this.name)
const disabledNodes = Object.entries(this.editor.schema.nodes)
.map(([, value]) => value)
.filter((node) => this.options.notAfter.includes(node.name))
return [
new Plugin({
key: plugin,
appendTransaction: (_, __, state) => {
const { doc, tr, schema } = state
const shouldInsertNodeAtEnd = plugin.getState(state)
const endPosition = doc.content.size
const type = schema.nodes[this.options.node]
if (!shouldInsertNodeAtEnd) {
return
}
return tr.insert(endPosition, type.create())
},
state: {
init: (_, state) => {
const lastNode = state.tr.doc.lastChild
return !nodeEqualsType({ node: lastNode, types: disabledNodes })
},
apply: (tr, value) => {
if (!tr.docChanged) {
return value
}
const lastNode = tr.doc.lastChild
return !nodeEqualsType({ node: lastNode, types: disabledNodes })
}
}
})
]
}
})

View File

@ -10,7 +10,7 @@ import { useRouter } from '../../stores/router'
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll' import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
import { splitToPages } from '../../utils/splitToPages' import { splitToPages } from '../../utils/splitToPages'
import styles from './Author.module.scss' import styles from './Author.module.scss'
import stylesArticle from '../../styles/Article.module.scss' import stylesArticle from '../Article/Article.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import Userpic from '../Author/Userpic' import Userpic from '../Author/Userpic'
import { Popup } from '../_shared/Popup' import { Popup } from '../_shared/Popup'

View File

@ -2,18 +2,14 @@ import { lazy, Suspense } from 'solid-js'
import { Loading } from '../_shared/Loading' import { Loading } from '../_shared/Loading'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
const Editor = lazy(() => import('../EditorNew/Editor')) const Editor = lazy(() => import('../Editor/Editor'))
export const CreateView = () => { export const CreateView = () => {
const { t } = useLocalize() const { t } = useLocalize()
const newArticleIpsum = `<h1>${t('Header')}</h1>
<h2>${t('Subheader')}</h2>
<p>${t('A short introduction to keep the reader interested')}</p>`
return ( return (
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
<Editor initialContent={newArticleIpsum} /> <Editor />
</Suspense> </Suspense>
) )
} }

View File

@ -29,7 +29,7 @@ export const FeedView = () => {
const { sortedAuthors } = useAuthorsStore() const { sortedAuthors } = useAuthorsStore()
const { topTopics } = useTopicsStore() const { topTopics } = useTopicsStore()
const { topAuthors } = useTopAuthorsStore() const { topAuthors } = useTopAuthorsStore()
const { session } = useSession() const { session, userSlug } = useSession()
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const [topComments, setTopComments] = createSignal<Reaction[]>([]) const [topComments, setTopComments] = createSignal<Reaction[]>([])
@ -48,7 +48,6 @@ export const FeedView = () => {
// } // }
// }) // })
const userSlug = createMemo(() => session()?.user?.slug)
createEffect(async () => { createEffect(async () => {
if (userSlug()) { if (userSlug()) {
// load recent editing shouts ( visibility = authors ) // load recent editing shouts ( visibility = authors )

View File

@ -69,10 +69,9 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
const deleteReaction = async (id: number): Promise<void> => { const deleteReaction = async (id: number): Promise<void> => {
const reaction = await apiClient.destroyReaction(id) const reaction = await apiClient.destroyReaction(id)
setReactionEntities((oldState) => ({ setReactionEntities({
...oldState,
[reaction.id]: undefined [reaction.id]: undefined
})) })
} }
const updateReaction = async (id: number, input: ReactionInput): Promise<void> => { const updateReaction = async (id: number, input: ReactionInput): Promise<void> => {