editor very WIP
This commit is contained in:
parent
a7b0df4b24
commit
195781741c
8047
package-lock.json
generated
8047
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
67
package.json
67
package.json
|
@ -33,19 +33,19 @@
|
|||
"@aws-sdk/client-s3": "^3.282.0",
|
||||
"@aws-sdk/lib-storage": "^3.282.0",
|
||||
"formidable": "^2.1.1",
|
||||
"i18next": "^22.4.10",
|
||||
"mailgun.js": "^8.2.0"
|
||||
"i18next": "^22.4.11",
|
||||
"mailgun.js": "^8.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.0",
|
||||
"@graphql-codegen/cli": "^3.2.1",
|
||||
"@graphql-codegen/typescript": "^3.0.1",
|
||||
"@graphql-codegen/typescript-operations": "^3.0.1",
|
||||
"@graphql-codegen/cli": "^3.2.2",
|
||||
"@graphql-codegen/typescript": "^3.0.2",
|
||||
"@graphql-codegen/typescript-operations": "^3.0.2",
|
||||
"@graphql-codegen/typescript-urql": "^3.7.3",
|
||||
"@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",
|
||||
"@nanostores/router": "^0.8.1",
|
||||
"@nanostores/router": "^0.8.2",
|
||||
"@nanostores/solid": "^0.3.2",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@solid-primitives/memo": "^1.2.0",
|
||||
|
@ -53,15 +53,44 @@
|
|||
"@solid-primitives/storage": "^1.3.7",
|
||||
"@solid-primitives/upload": "^0.0.109",
|
||||
"@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/node": "^18.14.6",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.0",
|
||||
"@typescript-eslint/parser": "^5.54.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
||||
"@typescript-eslint/parser": "^5.54.1",
|
||||
"@urql/core": "^3.1.1",
|
||||
"@urql/devtools": "^2.0.3",
|
||||
"@urql/exchange-graphcache": "^5.0.9",
|
||||
"babel-preset-solid": "^1.5.6",
|
||||
"babel-preset-solid": "^1.6.12",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bootstrap": "^5.2.3",
|
||||
"clsx": "^1.2.1",
|
||||
|
@ -75,18 +104,19 @@
|
|||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.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-unicorn": "^46.0.0",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"graphql-ws": "^5.11.2",
|
||||
"graphql-ws": "^5.12.0",
|
||||
"hast-util-select": "^5.0.4",
|
||||
"husky": "^8.0.3",
|
||||
"hygen": "^6.2.11",
|
||||
"i18next-http-backend": "^2.1.1",
|
||||
"idb": "^7.1.1",
|
||||
"jest": "^29.4.3",
|
||||
"install": "^0.13.0",
|
||||
"jest": "^29.5.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lint-staged": "^13.1.2",
|
||||
"loglevel": "^1.8.1",
|
||||
|
@ -97,6 +127,7 @@
|
|||
"markdown-it-mark": "^3.0.1",
|
||||
"markdown-it-replace-link": "^1.1.0",
|
||||
"nanostores": "^0.7.4",
|
||||
"npm": "^9.6.0",
|
||||
"orderedmap": "^2.1.0",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-eslint": "^15.0.1",
|
||||
|
@ -116,14 +147,15 @@
|
|||
"rollup": "^3.18.0",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
"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",
|
||||
"sort-package-json": "^2.3.0",
|
||||
"stylelint": "^15.2.0",
|
||||
"stylelint-config-css-modules": "^4.1.0",
|
||||
"stylelint-config-prettier-scss": "^0.0.1",
|
||||
"stylelint-config-standard-scss": "^7.0.1",
|
||||
"stylelint-order": "^6.0.1",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"stylelint-scss": "^4.4.0",
|
||||
"swiper": "^8.4.7",
|
||||
"ts-node": "^10.9.1",
|
||||
|
@ -134,10 +166,11 @@
|
|||
"vite": "^4.1.4",
|
||||
"vite-plugin-sass-dts": "^1.2.16",
|
||||
"vite-plugin-solid": "^2.6.1",
|
||||
"vite-plugin-ssr": "^0.4.90",
|
||||
"vite-plugin-ssr": "^0.4.91",
|
||||
"wonka": "^6.2.3",
|
||||
"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-webrtc": "^10.2.4",
|
||||
"yjs": "^13.5.48"
|
||||
|
|
|
@ -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",
|
||||
"zine": "zine",
|
||||
"By time": "By time",
|
||||
"New only": "New only"
|
||||
"New only": "New only",
|
||||
"Short opening": "Short opening"
|
||||
}
|
||||
|
|
|
@ -249,5 +249,6 @@
|
|||
"view": "просмотр",
|
||||
"zine": "журнал",
|
||||
"By time": "По порядку",
|
||||
"New only": "Только новые"
|
||||
"New only": "Только новые",
|
||||
"Short opening": "Небольшое вступление, чтобы заинтересовать читателя"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Show, createMemo, createSignal, onMount, For } from 'solid-js'
|
||||
import { Comment } from './Comment'
|
||||
import styles from '../../styles/Article.module.scss'
|
||||
import styles from './Article.module.scss'
|
||||
import { clsx } from 'clsx'
|
||||
import { Author, Reaction, ReactionKind } from '../../graphql/types.gen'
|
||||
import { useSession } from '../../context/session'
|
||||
|
@ -55,6 +55,8 @@ export const CommentsTree = (props: Props) => {
|
|||
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT')
|
||||
)
|
||||
|
||||
console.log(JSON.parse(JSON.stringify(reactionEntities)))
|
||||
|
||||
const sortedComments = createMemo(() => {
|
||||
let newSortedComments = [...comments()]
|
||||
newSortedComments = newSortedComments.sort(byCreated)
|
||||
|
|
|
@ -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%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import { capitalize, formatDate } from '../../utils'
|
||||
import './Full.scss'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { AuthorCard } from '../Author/Card'
|
||||
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 { SharePopup } from './SharePopup'
|
||||
import { getDescription } from '../../utils/meta'
|
||||
import stylesHeader from '../Nav/Header.module.scss'
|
||||
import styles from '../../styles/Article.module.scss'
|
||||
import { ShoutRatingControl } from './ShoutRatingControl'
|
||||
import { clsx } from 'clsx'
|
||||
import { CommentsTree } from './CommentsTree'
|
||||
|
@ -20,6 +17,8 @@ import { router } from '../../stores/router'
|
|||
import { useReactions } from '../../context/reactions'
|
||||
import { Title } from '@solidjs/meta'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import stylesHeader from '../Nav/Header.module.scss'
|
||||
import styles from './Article.module.scss'
|
||||
|
||||
interface ArticleProps {
|
||||
article: Shout
|
||||
|
@ -108,7 +107,7 @@ export const FullArticle = (props: ArticleProps) => {
|
|||
return (
|
||||
<>
|
||||
<Title>{props.article.title}</Title>
|
||||
<div class="shout wide-container">
|
||||
<div class="wide-container">
|
||||
<article class="col-md-6 shift-content">
|
||||
<div class={styles.shoutHeader}>
|
||||
<div class={styles.shoutTopic}>
|
||||
|
@ -168,7 +167,7 @@ export const FullArticle = (props: ArticleProps) => {
|
|||
<Show when={media() && props.article.layout === 'image'}>
|
||||
<Slider slidesPerView={1} isPageGallery={true} isCardsWithCover={true} hasThumbs={true}>
|
||||
<For each={media() || []}>
|
||||
{(m: MediaItem) => (
|
||||
{(m) => (
|
||||
<div class="swiper-slide">
|
||||
<div class="swiper-slide__inner">
|
||||
<img src={m.url || m.pic} alt={m.title} loading="lazy" />
|
||||
|
@ -181,7 +180,7 @@ export const FullArticle = (props: ArticleProps) => {
|
|||
</Slider>
|
||||
</Show>
|
||||
|
||||
<div class="shout wide-container">
|
||||
<div class=" wide-container">
|
||||
<div class="col-md-8 shift-content">
|
||||
<div class={styles.shoutStats}>
|
||||
<div class={styles.shoutStatsItem}>
|
||||
|
|
|
@ -35,7 +35,10 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
|||
|
||||
const shoutRatingReactions = createMemo(() =>
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
|
|
7
src/components/Editor/Editor.module.scss
Normal file
7
src/components/Editor/Editor.module.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
.container {
|
||||
width: 680px;
|
||||
}
|
||||
|
||||
.editor {
|
||||
width: 100%;
|
||||
}
|
132
src/components/Editor/Editor.tsx
Normal file
132
src/components/Editor/Editor.tsx
Normal 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
|
0
src/components/Editor/EditorBubbleMenu.module.scss
Normal file
0
src/components/Editor/EditorBubbleMenu.module.scss
Normal file
14
src/components/Editor/EditorBubbleMenu.tsx
Normal file
14
src/components/Editor/EditorBubbleMenu.tsx
Normal 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>
|
||||
)
|
||||
}
|
4
src/components/Editor/EditorFloatingMenu.module.scss
Normal file
4
src/components/Editor/EditorFloatingMenu.module.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
.editorFloatingMenu {
|
||||
position: relative;
|
||||
left: -100%;
|
||||
}
|
15
src/components/Editor/EditorFloatingMenu.tsx
Normal file
15
src/components/Editor/EditorFloatingMenu.tsx
Normal 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>
|
||||
)
|
||||
}
|
14
src/components/Editor/Prosemirror.scss
Normal file
14
src/components/Editor/Prosemirror.scss
Normal 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;
|
||||
}
|
69
src/components/Editor/extensions/TrailingNode.tsx
Normal file
69
src/components/Editor/extensions/TrailingNode.tsx
Normal 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 })
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
|
@ -10,7 +10,7 @@ import { useRouter } from '../../stores/router'
|
|||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||
import { splitToPages } from '../../utils/splitToPages'
|
||||
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 Userpic from '../Author/Userpic'
|
||||
import { Popup } from '../_shared/Popup'
|
||||
|
|
|
@ -2,18 +2,14 @@ import { lazy, Suspense } from 'solid-js'
|
|||
import { Loading } from '../_shared/Loading'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
|
||||
const Editor = lazy(() => import('../EditorNew/Editor'))
|
||||
const Editor = lazy(() => import('../Editor/Editor'))
|
||||
|
||||
export const CreateView = () => {
|
||||
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 (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Editor initialContent={newArticleIpsum} />
|
||||
<Editor />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ export const FeedView = () => {
|
|||
const { sortedAuthors } = useAuthorsStore()
|
||||
const { topTopics } = useTopicsStore()
|
||||
const { topAuthors } = useTopAuthorsStore()
|
||||
const { session } = useSession()
|
||||
const { session, userSlug } = useSession()
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
const [topComments, setTopComments] = createSignal<Reaction[]>([])
|
||||
|
||||
|
@ -48,7 +48,6 @@ export const FeedView = () => {
|
|||
// }
|
||||
// })
|
||||
|
||||
const userSlug = createMemo(() => session()?.user?.slug)
|
||||
createEffect(async () => {
|
||||
if (userSlug()) {
|
||||
// load recent editing shouts ( visibility = authors )
|
||||
|
|
|
@ -69,10 +69,9 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
|||
|
||||
const deleteReaction = async (id: number): Promise<void> => {
|
||||
const reaction = await apiClient.destroyReaction(id)
|
||||
setReactionEntities((oldState) => ({
|
||||
...oldState,
|
||||
setReactionEntities({
|
||||
[reaction.id]: undefined
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
const updateReaction = async (id: number, input: ReactionInput): Promise<void> => {
|
||||
|
|
Loading…
Reference in New Issue
Block a user