reset1
This commit is contained in:
parent
219e3e2325
commit
1e4138e40e
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -28,3 +28,4 @@ target
|
|||
.output
|
||||
.vinxi
|
||||
*.pem
|
||||
edge.*
|
||||
|
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch browser against localhost",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "https://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}/src",
|
||||
"sourceMaps": true,
|
||||
"trace": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -30,12 +30,11 @@ export default defineConfig({
|
|||
https: true
|
||||
},
|
||||
devOverlay: true,
|
||||
build: {
|
||||
chunkSizeWarningLimit: 1024,
|
||||
target: 'esnext'
|
||||
},
|
||||
vite: {
|
||||
build: {
|
||||
sourcemap: true,
|
||||
chunkSizeWarningLimit: 1024,
|
||||
target: 'esnext'
|
||||
},
|
||||
envPrefix: 'PUBLIC_',
|
||||
plugins: [!isVercel && mkcert(), nodePolyfills(polyfillOptions), sassDts()],
|
||||
css: {
|
||||
|
|
|
@ -11,7 +11,7 @@ generates:
|
|||
skipTypename: true
|
||||
useTypeImports: true
|
||||
outputPath: './src/graphql/types/chat.gen.ts'
|
||||
# namingConvention: lodash#pascalCase
|
||||
# namingConvention: change-case#CamelCase # for generated types
|
||||
|
||||
# Generate types for core
|
||||
src/graphql/schema/core.gen.ts:
|
||||
|
@ -24,7 +24,7 @@ generates:
|
|||
skipTypename: true
|
||||
useTypeImports: true
|
||||
outputPath: './src/graphql/types/core.gen.ts'
|
||||
# namingConvention: lodash#pascalCase
|
||||
# namingConvention: change-case#CamelCase # for generated types
|
||||
hooks:
|
||||
afterAllFileWrite:
|
||||
- prettier --ignore-path .gitignore --write --plugin-search-dir=. src/graphql/schema/*.gen.ts
|
||||
|
|
380
package-lock.json
generated
380
package-lock.json
generated
|
@ -21,7 +21,7 @@
|
|||
"@graphql-codegen/typescript-operations": "^4.2.3",
|
||||
"@graphql-codegen/typescript-urql": "^4.0.0",
|
||||
"@hocuspocus/provider": "^2.13.5",
|
||||
"@playwright/test": "^1.45.2",
|
||||
"@playwright/test": "^1.45.3",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@solid-primitives/media": "^2.2.9",
|
||||
"@solid-primitives/memo": "^1.3.9",
|
||||
|
@ -33,35 +33,35 @@
|
|||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.14.1",
|
||||
"@solidjs/start": "^1.0.6",
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/extension-blockquote": "^2.5.4",
|
||||
"@tiptap/extension-bold": "^2.5.4",
|
||||
"@tiptap/extension-bubble-menu": "^2.5.4",
|
||||
"@tiptap/extension-bullet-list": "^2.5.4",
|
||||
"@tiptap/extension-character-count": "^2.5.4",
|
||||
"@tiptap/extension-collaboration": "^2.5.4",
|
||||
"@tiptap/extension-collaboration-cursor": "^2.5.4",
|
||||
"@tiptap/extension-document": "^2.5.4",
|
||||
"@tiptap/extension-dropcursor": "^2.5.4",
|
||||
"@tiptap/extension-floating-menu": "^2.5.4",
|
||||
"@tiptap/extension-focus": "^2.5.4",
|
||||
"@tiptap/extension-gapcursor": "^2.5.4",
|
||||
"@tiptap/extension-hard-break": "^2.5.4",
|
||||
"@tiptap/extension-heading": "^2.5.4",
|
||||
"@tiptap/extension-highlight": "^2.5.4",
|
||||
"@tiptap/extension-history": "^2.5.4",
|
||||
"@tiptap/extension-horizontal-rule": "^2.5.4",
|
||||
"@tiptap/extension-image": "^2.5.4",
|
||||
"@tiptap/extension-italic": "^2.5.4",
|
||||
"@tiptap/extension-link": "^2.5.4",
|
||||
"@tiptap/extension-list-item": "^2.5.4",
|
||||
"@tiptap/extension-ordered-list": "^2.5.4",
|
||||
"@tiptap/extension-paragraph": "^2.5.4",
|
||||
"@tiptap/extension-placeholder": "^2.5.4",
|
||||
"@tiptap/extension-strike": "^2.5.4",
|
||||
"@tiptap/extension-text": "^2.5.4",
|
||||
"@tiptap/extension-underline": "^2.5.4",
|
||||
"@tiptap/extension-youtube": "^2.5.4",
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/extension-blockquote": "^2.5.5",
|
||||
"@tiptap/extension-bold": "^2.5.5",
|
||||
"@tiptap/extension-bubble-menu": "^2.5.5",
|
||||
"@tiptap/extension-bullet-list": "^2.5.5",
|
||||
"@tiptap/extension-character-count": "^2.5.5",
|
||||
"@tiptap/extension-collaboration": "^2.5.5",
|
||||
"@tiptap/extension-collaboration-cursor": "^2.5.5",
|
||||
"@tiptap/extension-document": "^2.5.5",
|
||||
"@tiptap/extension-dropcursor": "^2.5.5",
|
||||
"@tiptap/extension-floating-menu": "^2.5.5",
|
||||
"@tiptap/extension-focus": "^2.5.5",
|
||||
"@tiptap/extension-gapcursor": "^2.5.5",
|
||||
"@tiptap/extension-hard-break": "^2.5.5",
|
||||
"@tiptap/extension-heading": "^2.5.5",
|
||||
"@tiptap/extension-highlight": "^2.5.5",
|
||||
"@tiptap/extension-history": "^2.5.5",
|
||||
"@tiptap/extension-horizontal-rule": "^2.5.5",
|
||||
"@tiptap/extension-image": "^2.5.5",
|
||||
"@tiptap/extension-italic": "^2.5.5",
|
||||
"@tiptap/extension-link": "^2.5.5",
|
||||
"@tiptap/extension-list-item": "^2.5.5",
|
||||
"@tiptap/extension-ordered-list": "^2.5.5",
|
||||
"@tiptap/extension-paragraph": "^2.5.5",
|
||||
"@tiptap/extension-placeholder": "^2.5.5",
|
||||
"@tiptap/extension-strike": "^2.5.5",
|
||||
"@tiptap/extension-text": "^2.5.5",
|
||||
"@tiptap/extension-underline": "^2.5.5",
|
||||
"@tiptap/extension-youtube": "^2.5.5",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/cookie-signature": "^1.1.2",
|
||||
"@types/node": "^20.14.11",
|
||||
|
@ -98,7 +98,7 @@
|
|||
"swiper": "^11.1.5",
|
||||
"throttle-debounce": "^5.0.2",
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript": "^5.5.4",
|
||||
"typograf": "^7.4.1",
|
||||
"uniqolor": "^1.1.1",
|
||||
"vinxi": "^0.4.1",
|
||||
|
@ -2604,9 +2604,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/delegate": {
|
||||
"version": "10.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.0.14.tgz",
|
||||
"integrity": "sha512-mYrLtwVKTHg5F4OFrJbiL5F7dzopzGiac5ezkVrnlGNPBQ8GNCr1zo32c1rYyIbsa8fJSUvAJfJfFj6ipnutnw==",
|
||||
"version": "10.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.0.15.tgz",
|
||||
"integrity": "sha512-18R4vcJWz/6pk6K9SslijR0jCSe0mAnSs0sd1eioTvSSCWiagPdCOOhaM9dPNfEnxp3TRHg3cnYqywRtJgKHvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -4148,13 +4148,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.45.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.2.tgz",
|
||||
"integrity": "sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==",
|
||||
"version": "1.45.3",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz",
|
||||
"integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.45.2"
|
||||
"playwright": "1.45.3"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
@ -4956,9 +4956,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@tiptap/core": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.5.4.tgz",
|
||||
"integrity": "sha512-Zs/hShr4+W02+0nOlpmr5cS2YjDRLqd+XMt+jsiQH0QNr3s1Lc82pfF6C3CjgLEZtdUzImZrW2ABtLlpvbogaA==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.5.5.tgz",
|
||||
"integrity": "sha512-VnAnyWnsqN65QijtUFHbe7EPSJCkhNEAwlatsG/HvrZvUv9KmoWWbMsHAU73wozKzPXR3nHRbCxN+LuxP5bADg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -4966,13 +4966,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-blockquote": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.5.4.tgz",
|
||||
"integrity": "sha512-UqeJunZM3IiCQGZE0X5YNUOWYkuIieqrwPgOEghAIjnhDcQizQcouRQ5R7cwwv/scNr2JvZHncOTLrALV3Janw==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.5.5.tgz",
|
||||
"integrity": "sha512-K+fc++ASlgDRHN6i3j3JBGzWiDhhoZv0jCUB/l7Jzut4UfjIoWqKhmJajnp95Qu9tmwQUy9LMzHqG4G5wUsIsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -4980,13 +4980,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bold": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.5.4.tgz",
|
||||
"integrity": "sha512-H5sjqloFMjq7VOSfE+U4T7dqGoflOiF6RW6/gZm/U6KYeHG2/bG0ktq7mWAnnhbiKiy7gUcxyJCV+ILdGX9C5g==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.5.5.tgz",
|
||||
"integrity": "sha512-vXqaeTKy4nf4X+s7NkFt0OsuS1eKMQhrdt7SzACf0gWi3M761WGkaKHy8XUlo7zhWhqHtkgey53Gaw0nbEY54Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -4994,13 +4994,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bubble-menu": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.5.4.tgz",
|
||||
"integrity": "sha512-GHwef912K1yd75pp9JGDnKSp1DvdOHH8BcHQv0no+a3q2ePFPYcgaSwVRR59jHRX9WzdVfoLcqDSAeoNGOrISw==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.5.5.tgz",
|
||||
"integrity": "sha512-7k0HqrnhQGVZk86MEc5vt8stNRxIY65AMjZfszY/mQw0Dza7EQig/9b/AEmi9n+TNW5/8Qu+OMJD9ln92d/Eog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -5011,14 +5011,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bullet-list": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.5.4.tgz",
|
||||
"integrity": "sha512-aAfpALeD6OxymkbtrzDqbgkAkzVVHudxOb8GsK1N6m42nFL7Q9JzHJ5/8KzB+xi25CcIbS+HmXJkRIQJXgNbSA==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.5.5.tgz",
|
||||
"integrity": "sha512-p89cTmGUoq3OEFzcS49iQ/tyQjDoKW1J0c7EghS7eU3wHVxeo/Ke110cY2W5o1e4KMFowo3a4jVsxKuCQJkWrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5026,13 +5026,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-character-count": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.5.4.tgz",
|
||||
"integrity": "sha512-6qwt+81I+y+t3eoFPmCG2ouQce2RccwyiUC0ZOPTG1eUB+5yXmyIwBYI4aOM4TEfxNizyaZtQw32CDdAhMr3YA==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.5.5.tgz",
|
||||
"integrity": "sha512-rh6q3YeuLV8PnaKUqQbnOQ16obXPcqsqnQ+y1XLWH74lHwdvbOvE1BCvSZD0ULPI9EcOtvhdZEZkDxlqQ9H3jg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5040,14 +5040,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-collaboration": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.5.4.tgz",
|
||||
"integrity": "sha512-CpQdbr7XpQaVqRFo/A1DchrQZMDb8vrkP+FcUIgvHN0b8hwKDmXRAHDtuk8yTTEatW1EqpX8lx8UxaUTcDNbIg==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.5.5.tgz",
|
||||
"integrity": "sha512-HpDW+1VTKdtK7BglQNLFv2UzJIxtzZ9zvT+wdYDWPB3ZstoL8drpp4wGP2xt3tbki6wzGpUFkDCpVNl0oOunXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5055,15 +5055,15 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4",
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5",
|
||||
"y-prosemirror": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-collaboration-cursor": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.5.4.tgz",
|
||||
"integrity": "sha512-M32JChnP5RVdr1n+Tf0gF9bxx0gHvc0uV4SDxCMN3uaNH5YpcofmvKElS60rDGVfCdRTId/aj7P3AtwrvRlYdQ==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.5.5.tgz",
|
||||
"integrity": "sha512-DWX3eOplWyLegOWeZa0CAVbb9/UYbngiZyKjVMpDlx5qzhUuLL+Df54/UGKqB1ZrBZrxKCVQE3APMyXkxI/2VQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5071,14 +5071,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"y-prosemirror": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-document": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.5.4.tgz",
|
||||
"integrity": "sha512-4RDrhASxCTOZETYhIhEW1TfZqx3Tm+LQxouvBMFyODmT1PSgsg5Xz1FYpDPr+J49bGAK0Pr9ae0XcGW011L3sA==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.5.5.tgz",
|
||||
"integrity": "sha512-MIjYO63JepcJW37PQuKVmYuZFqkQOZ/12tV0YLU4o6gmGVdqJS0+3md9CdnyUFUDIo7x6TBh8r5i5L2xQpm3Sg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5086,13 +5086,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-dropcursor": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.5.4.tgz",
|
||||
"integrity": "sha512-jzSnuuYhlc0SsHvAteWkE9TJy3eRwkxQs4MO2JxALOzJECN4G82nlX8vciihBD6xf7lVgVSBACejK9+rsTHqCg==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.5.5.tgz",
|
||||
"integrity": "sha512-+K/qd115c3zFgHdvxtOkZhSTKNyPpjM0Np2v4cehqn0j+/3stOMGlAH2Jm/b2L8RylFKGtQP1b/1wsKY5feuAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5100,14 +5100,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-floating-menu": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.5.4.tgz",
|
||||
"integrity": "sha512-EqD4rgi3UhnDcV3H1+ndAS4Ue2zpsU7hFKoevOIV6GS7xVnWN70AGt6swH24QzuHKKISFtWoLpKjrwRORNIxuA==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.5.5.tgz",
|
||||
"integrity": "sha512-1mgpxZGfy1ziNSvWz6m1nGb9ZF9fVVz4X4XwrIqwGw1Vqt9oXflm6puglnzwVLDeaMDT014VUfczJ4My3wDZzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -5118,14 +5118,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-focus": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-focus/-/extension-focus-2.5.4.tgz",
|
||||
"integrity": "sha512-/Iq++93f9S+bNJzj3OmgOydCO58VfAhmnsImbGK/GmxV39hHbgJdazxMugwdQlvrY/oe3+Y+WY8ZI1WlWwTJ4g==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-focus/-/extension-focus-2.5.5.tgz",
|
||||
"integrity": "sha512-c5ul5PNl/2HcYwEPu1kjjs/u8N5BtLnreeUyb223y8i4BEcjydVlnCfVVUdonQIWnj0mKQ8KZbyLTSYdijDsVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5133,14 +5133,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-gapcursor": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.5.4.tgz",
|
||||
"integrity": "sha512-wzTh1piODZBS0wmuDgPjjg8PQwclYa5LssnxDIo9pDSnt4l3AfHSAJIJSGIfgt96KnzF1wqRTRpe08qNa1n7/g==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.5.5.tgz",
|
||||
"integrity": "sha512-An/HwTheUP+D4UU1GVy2e4ypqA1TanZ7haNcm5WB+wSZQo6UNPIszIa49TTGenkk86hP2DH9cQSlTREsyAW6wg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5148,14 +5148,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-hard-break": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.5.4.tgz",
|
||||
"integrity": "sha512-nLn6HP9tqgdGGwbMORXVtcY30DTGctYFaWADRthvBjVgacYSeKlhUcsSu3YgaxtbxZp6BhfRvD2kKrxyQsSjnQ==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.5.5.tgz",
|
||||
"integrity": "sha512-VtrwKU0LYS/0rfH5rGz8ztKwA0bsHRyBF53G7aP2FS4BiN8aOEu8t7VkvBZAewXDITDah9K6rqfXk+MNwoul2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5163,13 +5163,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-heading": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.5.4.tgz",
|
||||
"integrity": "sha512-DuAB58/e7eho1rkyad0Z/SjW+EB+H2hRqHlswEeZZYhBTjzey5UmBwkMWTGC/SQiRisx1xYQYTd8T0fiABi5hw==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.5.5.tgz",
|
||||
"integrity": "sha512-NDnXOR6HmnkBA68oZTVf0BT5t8ikVFv9X6Ft/O5oU6IuzCswS8BUb5MJIhKBWQXJTsCNbC6EYl5jhJ3hukLcHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5177,13 +5177,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-highlight": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.5.4.tgz",
|
||||
"integrity": "sha512-TSYnFBluZu1YQdTCyXl2wuxFuhFUYFzbaV0f1wq2P2Nc8U2OiiuaNz+QggHw5Hf3ILzkRxQCUQnq97Q/5smMwQ==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.5.5.tgz",
|
||||
"integrity": "sha512-NqMmL9/82288DI1trnuxB3hcf61x+iDKFvNAE+thW6MmY6ZWi47bEnfUQGwDeInxH81NfMhTTSxuXmnuO10noQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5191,13 +5191,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-history": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.5.4.tgz",
|
||||
"integrity": "sha512-WB1fZYGIlpahAD6Ba+mj9vIb1tk8S3TsADXDFKxLVpZWZPQ+B7duGJP7g/vRH2XAXEs836JzC2oxjKeaop3k7A==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.5.5.tgz",
|
||||
"integrity": "sha512-CYxFpE9wayc+iZQIlXd3cbq47WP+KqjDhprbKF5Tb7+WoWLS2FB5WK3n+r/SrcoIaslIt5SYDRQPzx4fS3N7LA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5205,14 +5205,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-horizontal-rule": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.5.4.tgz",
|
||||
"integrity": "sha512-uXLDe/iyzQbyfDkJ8kE5XaAkY3EOcbTFLjbueqGlkbWtjJgy+3LysGvh8fQj8PAOaIBMaFRFhTq7GMbW2ebRog==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.5.5.tgz",
|
||||
"integrity": "sha512-8oV0oLgGwJqr44wk7+bHxTAenR0bvk9aVdmE/owg1oy2tkSX0bwtvQEOnwwxtfPJGTwq8JGhefUGYcpHfG2YYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5220,14 +5220,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-image": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.5.4.tgz",
|
||||
"integrity": "sha512-4ySSP7iPsbbo1SlPJYj546TKettuO6FGY5MQKxH8AGnZWyQGZYl89GpU1iGFAaeHq4dKUemM5D3ikgSynEQLow==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.5.5.tgz",
|
||||
"integrity": "sha512-DvnKf3XCGf/2GQrqtwgKwgaeqIn2dXgHTire0E2aPj8T939jA4ApX5qLPumndHX0rAckX5VAbnJjQeoxtEmMFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5235,13 +5235,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-italic": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.5.4.tgz",
|
||||
"integrity": "sha512-TAhtl/fNBgv1elzF3HWES8uwVdpKBSYrq1e6yeYfj74mQn//3ksvdhWQrLzc1e+zcoHbk1PeOp/5ODdPuZ6tkg==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.5.5.tgz",
|
||||
"integrity": "sha512-PEeI68/u7Bm4n4xIcxVAV12jPhEa72fpHRnYfJe4CGp4x8mJfz/dowKN/P0/6CfjROB7Q8rY26u5E9fS+Cg73w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5249,13 +5249,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-link": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.5.4.tgz",
|
||||
"integrity": "sha512-xTB/+T6SHHCXInJni8WdqOfF40a/MiFUf5OoWW9cPrApx3I7TzJ9j8/WDshM0BOnDDw80w1bl9F2zkUQjC0Y2A==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.5.5.tgz",
|
||||
"integrity": "sha512-zVpNvMD8R9uW1SX1PJoj3fLyOHwuFWqiqEHN2KWfLbEnbL/KXNnpIyKdpHnI9lqFrsMf2dmyZCS3R6xIrynviQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -5266,14 +5266,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-list-item": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.5.4.tgz",
|
||||
"integrity": "sha512-bPxUCFt9HnAfoaZQgwqCfRAZ6L3QlYhIRDDbOvZag7IxCdQuZmeY4k5OZfQIGijNDTag7CN9cdL4fl9rnm6/sQ==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.5.5.tgz",
|
||||
"integrity": "sha512-CfNVCP8Pqqgr7fAQAuRvZikzXT9vCEogcW7/C16cyGykbUJBqBmpsyHcAlj7XwsBFUuJ5MCeULtk/0frUI5fMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5281,13 +5281,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-ordered-list": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.5.4.tgz",
|
||||
"integrity": "sha512-cl3cTJitY6yDUmxqgjDUtDWCyX1VVsZNJ6i9yiPeARcxvzFc81KmUJxTGl8WPT5TjqmM+TleRkZjsxgvXX57+Q==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.5.5.tgz",
|
||||
"integrity": "sha512-wElnGQJhKznayP7tVGl/r42mj1dLEeU+Ln1Y3wF/m+nFwKl2Gpsy01PjBy5sXPUgskGSWgMlOgJrQyMvH9AuAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5295,13 +5295,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-paragraph": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.5.4.tgz",
|
||||
"integrity": "sha512-pC1YIkkRPXoU0eDrhfAf8ZrFJQzvw2ftP6KRhLnnSw/Ot1DOjT1r95l7zsFefS9oCDMT/L4HghTAiPZ4rcpPbg==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.5.5.tgz",
|
||||
"integrity": "sha512-XZO1rqsU1vlt9qeG2pVVAt2gXjD0twl2D+uxy4Nw6gxqbhSgfbNq3RP72mmtcS4KyFJi7ETANpcRpb8ZNvXfmg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5309,13 +5309,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-placeholder": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.5.4.tgz",
|
||||
"integrity": "sha512-mcj4j2Z/L1H5dzWHbbWChuAdJK9F2p06fcjqL4iyJtVx38QQFzCdVmGaTAim8CLp/EynbAOYJ5gk9w2PTdv7+w==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.5.5.tgz",
|
||||
"integrity": "sha512-SwWLYdyrMeoVUQdivkIJ4kkAcb38pykxSetlrXitfUmnkwv0/fi+p76Rickf+roudWPsfzqvgvJ4gT6OAOJrGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5323,14 +5323,14 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/pm": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/pm": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-strike": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.5.4.tgz",
|
||||
"integrity": "sha512-OSN6ePbCwEhi3hYZZOPow/P9Ym2Kv3NhVbUvasjZCiqQuk8TGc33xirPWl9DTjb/BLfL66TtJ2tKUEVOKl5dKg==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.5.5.tgz",
|
||||
"integrity": "sha512-xnVdSsP7+4yQ1E+rI77ZHvzDH1Gwe2Ty1tgXeOaLjt3RfeVx4xy75o09yHzab6J4hgPebonoXKbZV0JVTGnjtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5338,13 +5338,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.5.4.tgz",
|
||||
"integrity": "sha512-+3x/hYqhmCYbvedCcQzQHFtZ5MAcMOlKuczomZtygf8AfDfuQVrG1m4GoJyNzJdqxjN80/xq4e2vDVvqQxYTCw==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.5.5.tgz",
|
||||
"integrity": "sha512-8c/hxcw7t/S3iKGSFwGNxC2I6AkKpRiySQJ95ML2miwSOAxWhnltoYYV7gobWCRgm25lnvzX/Z6BdpFzXBrBKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5352,13 +5352,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-underline": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.5.4.tgz",
|
||||
"integrity": "sha512-o8T3oWbniA3rLo6LkslPRF8pwdjsaHXJCeK4KmKeCyYhTpMfjypT3uptd+VSSJ4iQkaiFInKeIUOBqqEQ9cADw==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.5.5.tgz",
|
||||
"integrity": "sha512-3uog8d4G/AdqaJC8qutIIgkYnU2TfXW3QbtEy0Yg2WdjCz97bWXkFkNhhVZM/hvXjFCbYboRN5HLcIHl8+Zgmg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5366,13 +5366,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-youtube": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-youtube/-/extension-youtube-2.5.4.tgz",
|
||||
"integrity": "sha512-iHcvXOA32MZsVJTT7mvZ1CWKUo2quQMQXfBniizLm0lUG1ftSioqnDuXy4kEjeCBR2cnZr3yph6tbG/pF0RcHg==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-youtube/-/extension-youtube-2.5.5.tgz",
|
||||
"integrity": "sha512-dPLSLsEiMdXB5q0YDRJKWiiTqdFiSeyaC5qWLio4SHYfyTYT1+M2Wwox+5Dm/OSgCHpxpT2W8JRt+H4+P38t9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
@ -5380,13 +5380,13 @@
|
|||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.5.4"
|
||||
"@tiptap/core": "^2.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/pm": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.5.4.tgz",
|
||||
"integrity": "sha512-oFIsuniptdUXn93x4aM2sVN3hYKo9Fj55zAkYrWhwxFYUYcPxd5ibra2we+wRK5TaiPu098wpC+yMSTZ/KKMpA==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.5.5.tgz",
|
||||
"integrity": "sha512-ppePiLaeG6IKkm8Yq+mRENT4LIAS4qQyLT8EnKadznaTL6SNj/72mm0MjD44URkM38ySzIyvt/vqHDapNK0Hww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
|
@ -7057,9 +7057,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001642",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz",
|
||||
"integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==",
|
||||
"version": "1.0.30001643",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz",
|
||||
"integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -8441,9 +8441,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.832",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.832.tgz",
|
||||
"integrity": "sha512-cTen3SB0H2SGU7x467NRe1eVcQgcuS6jckKfWJHia2eo0cHIGOqHoAxevIYZD4eRHcWjkvFzo93bi3vJ9W+1lA==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz",
|
||||
"integrity": "sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
@ -11898,9 +11898,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz",
|
||||
"integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==",
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -12682,13 +12682,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.45.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.2.tgz",
|
||||
"integrity": "sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==",
|
||||
"version": "1.45.3",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.3.tgz",
|
||||
"integrity": "sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.2"
|
||||
"playwright-core": "1.45.3"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
@ -12701,9 +12701,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.45.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.2.tgz",
|
||||
"integrity": "sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==",
|
||||
"version": "1.45.3",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.3.tgz",
|
||||
"integrity": "sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -13891,9 +13891,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/immutable": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz",
|
||||
"integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==",
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
|
||||
"integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -15196,9 +15196,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.5.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
||||
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
|
64
package.json
64
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "discoursio-webapp",
|
||||
"private": true,
|
||||
"version": "0.9.6",
|
||||
"version": "0.9.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vinxi dev",
|
||||
|
@ -25,7 +25,7 @@
|
|||
"@graphql-codegen/typescript-operations": "^4.2.3",
|
||||
"@graphql-codegen/typescript-urql": "^4.0.0",
|
||||
"@hocuspocus/provider": "^2.13.5",
|
||||
"@playwright/test": "^1.45.2",
|
||||
"@playwright/test": "^1.45.3",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@solid-primitives/media": "^2.2.9",
|
||||
"@solid-primitives/memo": "^1.3.9",
|
||||
|
@ -37,35 +37,35 @@
|
|||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.14.1",
|
||||
"@solidjs/start": "^1.0.6",
|
||||
"@tiptap/core": "^2.5.4",
|
||||
"@tiptap/extension-blockquote": "^2.5.4",
|
||||
"@tiptap/extension-bold": "^2.5.4",
|
||||
"@tiptap/extension-bubble-menu": "^2.5.4",
|
||||
"@tiptap/extension-bullet-list": "^2.5.4",
|
||||
"@tiptap/extension-character-count": "^2.5.4",
|
||||
"@tiptap/extension-collaboration": "^2.5.4",
|
||||
"@tiptap/extension-collaboration-cursor": "^2.5.4",
|
||||
"@tiptap/extension-document": "^2.5.4",
|
||||
"@tiptap/extension-dropcursor": "^2.5.4",
|
||||
"@tiptap/extension-floating-menu": "^2.5.4",
|
||||
"@tiptap/extension-focus": "^2.5.4",
|
||||
"@tiptap/extension-gapcursor": "^2.5.4",
|
||||
"@tiptap/extension-hard-break": "^2.5.4",
|
||||
"@tiptap/extension-heading": "^2.5.4",
|
||||
"@tiptap/extension-highlight": "^2.5.4",
|
||||
"@tiptap/extension-history": "^2.5.4",
|
||||
"@tiptap/extension-horizontal-rule": "^2.5.4",
|
||||
"@tiptap/extension-image": "^2.5.4",
|
||||
"@tiptap/extension-italic": "^2.5.4",
|
||||
"@tiptap/extension-link": "^2.5.4",
|
||||
"@tiptap/extension-list-item": "^2.5.4",
|
||||
"@tiptap/extension-ordered-list": "^2.5.4",
|
||||
"@tiptap/extension-paragraph": "^2.5.4",
|
||||
"@tiptap/extension-placeholder": "^2.5.4",
|
||||
"@tiptap/extension-strike": "^2.5.4",
|
||||
"@tiptap/extension-text": "^2.5.4",
|
||||
"@tiptap/extension-underline": "^2.5.4",
|
||||
"@tiptap/extension-youtube": "^2.5.4",
|
||||
"@tiptap/core": "^2.5.5",
|
||||
"@tiptap/extension-blockquote": "^2.5.5",
|
||||
"@tiptap/extension-bold": "^2.5.5",
|
||||
"@tiptap/extension-bubble-menu": "^2.5.5",
|
||||
"@tiptap/extension-bullet-list": "^2.5.5",
|
||||
"@tiptap/extension-character-count": "^2.5.5",
|
||||
"@tiptap/extension-collaboration": "^2.5.5",
|
||||
"@tiptap/extension-collaboration-cursor": "^2.5.5",
|
||||
"@tiptap/extension-document": "^2.5.5",
|
||||
"@tiptap/extension-dropcursor": "^2.5.5",
|
||||
"@tiptap/extension-floating-menu": "^2.5.5",
|
||||
"@tiptap/extension-focus": "^2.5.5",
|
||||
"@tiptap/extension-gapcursor": "^2.5.5",
|
||||
"@tiptap/extension-hard-break": "^2.5.5",
|
||||
"@tiptap/extension-heading": "^2.5.5",
|
||||
"@tiptap/extension-highlight": "^2.5.5",
|
||||
"@tiptap/extension-history": "^2.5.5",
|
||||
"@tiptap/extension-horizontal-rule": "^2.5.5",
|
||||
"@tiptap/extension-image": "^2.5.5",
|
||||
"@tiptap/extension-italic": "^2.5.5",
|
||||
"@tiptap/extension-link": "^2.5.5",
|
||||
"@tiptap/extension-list-item": "^2.5.5",
|
||||
"@tiptap/extension-ordered-list": "^2.5.5",
|
||||
"@tiptap/extension-paragraph": "^2.5.5",
|
||||
"@tiptap/extension-placeholder": "^2.5.5",
|
||||
"@tiptap/extension-strike": "^2.5.5",
|
||||
"@tiptap/extension-text": "^2.5.5",
|
||||
"@tiptap/extension-underline": "^2.5.5",
|
||||
"@tiptap/extension-youtube": "^2.5.5",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/cookie-signature": "^1.1.2",
|
||||
"@types/node": "^20.14.11",
|
||||
|
@ -102,7 +102,7 @@
|
|||
"swiper": "^11.1.5",
|
||||
"throttle-debounce": "^5.0.2",
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript": "^5.5.4",
|
||||
"typograf": "^7.4.1",
|
||||
"uniqolor": "^1.1.1",
|
||||
"vinxi": "^0.4.1",
|
||||
|
|
|
@ -19,8 +19,7 @@ import {
|
|||
import { AuthorLink } from '../../Author/AuthorLink'
|
||||
import { Userpic } from '../../Author/Userpic'
|
||||
import { CommentDate } from '../CommentDate'
|
||||
import { RatingControl as CommentRatingControl } from '../RatingControl'
|
||||
|
||||
import { CommentRatingControl } from '../CommentRatingControl'
|
||||
import styles from './Comment.module.scss'
|
||||
|
||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
||||
|
|
122
src/components/Article/CommentRatingControl.tsx
Normal file
122
src/components/Article/CommentRatingControl.tsx
Normal file
|
@ -0,0 +1,122 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createMemo } from 'solid-js'
|
||||
|
||||
import { useFeed } from '~/context/feed'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useReactions } from '~/context/reactions'
|
||||
import { useSession } from '~/context/session'
|
||||
import { useSnackbar } from '~/context/ui'
|
||||
import { Reaction, ReactionKind } from '~/graphql/schema/core.gen'
|
||||
import { Popup } from '../_shared/Popup'
|
||||
import { VotersList } from '../_shared/VotersList'
|
||||
|
||||
import styles from './CommentRatingControl.module.scss'
|
||||
|
||||
type Props = {
|
||||
comment: Reaction
|
||||
}
|
||||
|
||||
export const CommentRatingControl = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { loadShout } = useFeed()
|
||||
const { session } = useSession()
|
||||
const uid = createMemo<number>(() => session()?.user?.app_data?.profile?.id || 0)
|
||||
const { showSnackbar } = useSnackbar()
|
||||
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
||||
|
||||
const checkReaction = (reactionKind: ReactionKind) =>
|
||||
Object.values(reactionEntities).some(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.created_by.id === uid() &&
|
||||
r.shout.id === props.comment.shout.id &&
|
||||
r.reply_to === props.comment.id
|
||||
)
|
||||
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
||||
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
|
||||
const canVote = createMemo(() => uid() !== props.comment.created_by.id)
|
||||
|
||||
const commentRatingReactions = createMemo(() =>
|
||||
Object.values(reactionEntities).filter(
|
||||
(r) =>
|
||||
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
|
||||
r.shout.id === props.comment.shout.id &&
|
||||
r.reply_to === props.comment.id
|
||||
)
|
||||
)
|
||||
|
||||
const deleteCommentReaction = async (reactionKind: ReactionKind) => {
|
||||
const reactionToDelete = Object.values(reactionEntities).find(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.created_by.id === uid() &&
|
||||
r.shout.id === props.comment.shout.id &&
|
||||
r.reply_to === props.comment.id
|
||||
)
|
||||
if (reactionToDelete) return deleteReaction(reactionToDelete.id)
|
||||
}
|
||||
|
||||
const handleRatingChange = async (isUpvote: boolean) => {
|
||||
try {
|
||||
if (isUpvoted()) {
|
||||
await deleteCommentReaction(ReactionKind.Like)
|
||||
} else if (isDownvoted()) {
|
||||
await deleteCommentReaction(ReactionKind.Dislike)
|
||||
} else {
|
||||
await createReaction({
|
||||
reaction: {
|
||||
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||
shout: props.comment.shout.id,
|
||||
reply_to: props.comment.id
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
showSnackbar({ type: 'error', body: t('Error') })
|
||||
}
|
||||
|
||||
await loadShout(props.comment.shout.slug)
|
||||
await loadReactionsBy({
|
||||
by: { shout: props.comment.shout.slug }
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={styles.commentRating}>
|
||||
<button
|
||||
role="button"
|
||||
disabled={!(canVote() && uid())}
|
||||
onClick={() => handleRatingChange(true)}
|
||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
|
||||
[styles.voted]: isUpvoted()
|
||||
})}
|
||||
/>
|
||||
<Popup
|
||||
trigger={
|
||||
<div
|
||||
class={clsx(styles.commentRatingValue, {
|
||||
[styles.commentRatingPositive]: (props.comment?.stat?.rating || 0) > 0,
|
||||
[styles.commentRatingNegative]: (props.comment?.stat?.rating || 0) < 0
|
||||
})}
|
||||
>
|
||||
{props.comment?.stat?.rating || 0}
|
||||
</div>
|
||||
}
|
||||
variant="tiny"
|
||||
>
|
||||
<VotersList
|
||||
reactions={commentRatingReactions()}
|
||||
fallbackMessage={t('This comment has not yet been rated')}
|
||||
/>
|
||||
</Popup>
|
||||
<button
|
||||
role="button"
|
||||
disabled={!(canVote() && uid())}
|
||||
onClick={() => handleRatingChange(false)}
|
||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
|
||||
[styles.voted]: isDownvoted()
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,15 +1,14 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { For, Show, createMemo, createSignal, lazy, onMount } from 'solid-js'
|
||||
|
||||
import { useFeed } from '~/context/feed'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { COMMENTS_PER_PAGE, useReactions } from '~/context/reactions'
|
||||
import { useReactions } from '~/context/reactions'
|
||||
import { useSession } from '~/context/session'
|
||||
import { Reaction, ReactionKind, ReactionSort, Shout } from '~/graphql/schema/core.gen'
|
||||
import { Author, Reaction, ReactionKind, ReactionSort } from '~/graphql/schema/core.gen'
|
||||
import { byCreated, byStat } from '~/lib/sort'
|
||||
import { SortFunction } from '~/types/common'
|
||||
import { Button } from '../_shared/Button'
|
||||
import { InlineLoader } from '../_shared/InlineLoader'
|
||||
import { LoadMoreItems, LoadMoreWrapper } from '../_shared/LoadMoreWrapper'
|
||||
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
|
||||
import styles from './Article.module.scss'
|
||||
import { Comment } from './Comment'
|
||||
|
@ -17,21 +16,21 @@ import { Comment } from './Comment'
|
|||
const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor'))
|
||||
|
||||
type Props = {
|
||||
shout: Shout
|
||||
articleAuthors: Author[]
|
||||
shoutSlug: string
|
||||
shoutId: number
|
||||
}
|
||||
|
||||
export const CommentsTree = (props: Props) => {
|
||||
const { session } = useSession()
|
||||
const { t } = useLocalize()
|
||||
const { reactionEntities, createReaction, loadShoutComments } = useReactions()
|
||||
const { seen } = useFeed()
|
||||
const [commentsOrder, setCommentsOrder] = createSignal<ReactionSort>(ReactionSort.Newest)
|
||||
const [onlyNew, setOnlyNew] = createSignal(false)
|
||||
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
|
||||
const [clearEditor, setClearEditor] = createSignal(false)
|
||||
const [clickedReplyId, setClickedReplyId] = createSignal<number>()
|
||||
const { reactionEntities, createReaction, loadReactionsBy } = useReactions()
|
||||
|
||||
const shoutLastSeen = createMemo(() => seen()[props.shout.slug] ?? 0)
|
||||
const comments = createMemo(() =>
|
||||
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT')
|
||||
)
|
||||
|
@ -49,9 +48,12 @@ export const CommentsTree = (props: Props) => {
|
|||
}
|
||||
return newSortedComments
|
||||
})
|
||||
const { seen } = useFeed()
|
||||
const shoutLastSeen = createMemo(() => seen()[props.shoutSlug] ?? 0)
|
||||
|
||||
onMount(() => {
|
||||
const currentDate = new Date()
|
||||
const setCookie = () => localStorage?.setItem(`${props.shout.slug}`, `${currentDate}`)
|
||||
const setCookie = () => localStorage?.setItem(`${props.shoutSlug}`, `${currentDate}`)
|
||||
if (!shoutLastSeen()) {
|
||||
setCookie()
|
||||
} else if (currentDate.getTime() > shoutLastSeen()) {
|
||||
|
@ -69,18 +71,6 @@ export const CommentsTree = (props: Props) => {
|
|||
}
|
||||
})
|
||||
const [posting, setPosting] = createSignal(false)
|
||||
const [commentsLoading, setCommentsLoading] = createSignal(false)
|
||||
const [pagination, setPagination] = createSignal(0)
|
||||
const loadMoreComments = async () => {
|
||||
setCommentsLoading(true)
|
||||
const next = pagination() + 1
|
||||
const offset = next * COMMENTS_PER_PAGE
|
||||
const rrr = await loadShoutComments(props.shout.id, COMMENTS_PER_PAGE, offset)
|
||||
rrr && setPagination(next)
|
||||
setCommentsLoading(false)
|
||||
return rrr as LoadMoreItems
|
||||
}
|
||||
|
||||
const handleSubmitComment = async (value: string) => {
|
||||
setPosting(true)
|
||||
try {
|
||||
|
@ -88,17 +78,18 @@ export const CommentsTree = (props: Props) => {
|
|||
reaction: {
|
||||
kind: ReactionKind.Comment,
|
||||
body: value,
|
||||
shout: props.shout.id
|
||||
shout: props.shoutId
|
||||
}
|
||||
})
|
||||
setClearEditor(true)
|
||||
await loadMoreComments()
|
||||
await loadReactionsBy({ by: { shout: props.shoutSlug } })
|
||||
} catch (error) {
|
||||
console.error('[handleCreate reaction]:', error)
|
||||
}
|
||||
setClearEditor(false)
|
||||
setPosting(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class={styles.commentsHeaderWrapper}>
|
||||
|
@ -136,33 +127,20 @@ export const CommentsTree = (props: Props) => {
|
|||
</ul>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={commentsLoading()}>
|
||||
<InlineLoader />
|
||||
</Show>
|
||||
<LoadMoreWrapper
|
||||
loadFunction={loadMoreComments}
|
||||
pageSize={COMMENTS_PER_PAGE}
|
||||
hidden={
|
||||
props.shout?.stat?.commented === 0 ||
|
||||
commentsLoading() ||
|
||||
comments().length >= (props.shout?.stat?.commented || 0)
|
||||
}
|
||||
>
|
||||
<ul class={styles.comments}>
|
||||
<For each={sortedComments().filter((r) => !r.reply_to)}>
|
||||
{(reaction) => (
|
||||
<Comment
|
||||
sortedComments={sortedComments()}
|
||||
isArticleAuthor={props.shout.authors?.some((a) => a && reaction.created_by.id === a.id)}
|
||||
comment={reaction}
|
||||
clickedReply={(id) => setClickedReplyId(id)}
|
||||
clickedReplyId={clickedReplyId()}
|
||||
lastSeen={shoutLastSeen()}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</LoadMoreWrapper>
|
||||
<ul class={styles.comments}>
|
||||
<For each={sortedComments().filter((r) => !r.reply_to)}>
|
||||
{(reaction) => (
|
||||
<Comment
|
||||
sortedComments={sortedComments()}
|
||||
isArticleAuthor={Boolean(props.articleAuthors.some((a) => a?.id === reaction.created_by.id))}
|
||||
comment={reaction}
|
||||
clickedReply={(id) => setClickedReplyId(id)}
|
||||
clickedReplyId={clickedReplyId()}
|
||||
lastSeen={shoutLastSeen()}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
<ShowIfAuthenticated
|
||||
fallback={
|
||||
<div class={styles.signInMessage}>
|
||||
|
|
|
@ -6,9 +6,10 @@ import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMou
|
|||
import { isServer } from 'solid-js/web'
|
||||
import { useFeed } from '~/context/feed'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useReactions } from '~/context/reactions'
|
||||
import { useSession } from '~/context/session'
|
||||
import { DEFAULT_HEADER_OFFSET, useUI } from '~/context/ui'
|
||||
import { type Author, type Maybe, type Shout, type Topic } from '~/graphql/schema/core.gen'
|
||||
import type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen'
|
||||
import { processPrepositions } from '~/intl/prepositions'
|
||||
import { isCyrillic } from '~/intl/translate'
|
||||
import { getImageUrl } from '~/lib/getThumbUrl'
|
||||
|
@ -32,8 +33,8 @@ import styles from './Article.module.scss'
|
|||
import { AudioHeader } from './AudioHeader'
|
||||
import { AudioPlayer } from './AudioPlayer'
|
||||
import { CommentsTree } from './CommentsTree'
|
||||
import { RatingControl as ShoutRatingControl } from './RatingControl'
|
||||
import { SharePopup, getShareUrl } from './SharePopup'
|
||||
import { ShoutRatingControl } from './ShoutRatingControl'
|
||||
|
||||
type Props = {
|
||||
article: Shout
|
||||
|
@ -62,11 +63,15 @@ const scrollTo = (el: HTMLElement) => {
|
|||
}
|
||||
|
||||
const imgSrcRegExp = /<img[^>]+src\s*=\s*["']([^"']+)["']/gi
|
||||
const COMMENTS_PER_PAGE = 30
|
||||
const VOTES_PER_PAGE = 50
|
||||
|
||||
export const FullArticle = (props: Props) => {
|
||||
const [searchParams, changeSearchParams] = useSearchParams<ArticlePageSearchParams>()
|
||||
const { showModal } = useUI()
|
||||
const { loadReactionsBy } = useReactions()
|
||||
const [selectedImage, setSelectedImage] = createSignal('')
|
||||
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
||||
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
||||
const { t, formatDate, lang } = useLocalize()
|
||||
const { session, requireAuthentication } = useSession()
|
||||
|
@ -74,6 +79,27 @@ export const FullArticle = (props: Props) => {
|
|||
const { addSeen } = useFeed()
|
||||
const formattedDate = createMemo(() => formatDate(new Date((props.article.published_at || 0) * 1000)))
|
||||
|
||||
const [pages, setPages] = createSignal<Record<string, number>>({})
|
||||
createEffect(
|
||||
on(
|
||||
pages,
|
||||
async (p: Record<string, number>) => {
|
||||
await loadReactionsBy({
|
||||
by: { shout: props.article.slug, comment: true },
|
||||
limit: COMMENTS_PER_PAGE,
|
||||
offset: COMMENTS_PER_PAGE * p.comments || 0
|
||||
})
|
||||
await loadReactionsBy({
|
||||
by: { shout: props.article.slug, rating: true },
|
||||
limit: VOTES_PER_PAGE,
|
||||
offset: VOTES_PER_PAGE * p.rating || 0
|
||||
})
|
||||
setIsReactionsLoaded(true)
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
const canEdit = createMemo(
|
||||
() =>
|
||||
Boolean(author()?.id) &&
|
||||
|
@ -141,7 +167,7 @@ export const FullArticle = (props: Props) => {
|
|||
let commentsRef: HTMLDivElement | undefined
|
||||
|
||||
createEffect(() => {
|
||||
if (searchParams?.commentId) {
|
||||
if (searchParams?.commentId && isReactionsLoaded()) {
|
||||
const commentElement = document.querySelector<HTMLElement>(
|
||||
`[id='comment_${searchParams?.commentId}']`
|
||||
)
|
||||
|
@ -280,16 +306,9 @@ export const FullArticle = (props: Props) => {
|
|||
})
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.article,
|
||||
() => {
|
||||
updateIframeSizes()
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
onMount(async () => {
|
||||
onMount(() => {
|
||||
console.debug(props.article)
|
||||
setPages((_) => ({ comments: 0, rating: 0 }))
|
||||
addSeen(props.article.slug)
|
||||
document.title = props.article.title
|
||||
updateIframeSizes()
|
||||
|
@ -560,7 +579,13 @@ export const FullArticle = (props: Props) => {
|
|||
</For>
|
||||
</div>
|
||||
<div id="comments" ref={(el) => (commentsRef = el)}>
|
||||
<CommentsTree shout={props.article} />
|
||||
<Show when={isReactionsLoaded()}>
|
||||
<CommentsTree
|
||||
shoutId={props.article.id}
|
||||
shoutSlug={props.article.slug}
|
||||
articleAuthors={props.article.authors as Author[]}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,254 +0,0 @@
|
|||
import { useSearchParams } from '@solidjs/router'
|
||||
import { clsx } from 'clsx'
|
||||
import { Show, createEffect, createMemo, createSignal, on } from 'solid-js'
|
||||
import { byCreated } from '~/lib/sort'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { RATINGS_PER_PAGE, useReactions } from '../../context/reactions'
|
||||
import { useSession } from '../../context/session'
|
||||
import { useSnackbar } from '../../context/ui'
|
||||
import { Reaction, ReactionKind, Shout } from '../../graphql/schema/core.gen'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { InlineLoader } from '../_shared/InlineLoader'
|
||||
import { LoadMoreItems, LoadMoreWrapper } from '../_shared/LoadMoreWrapper'
|
||||
import { Popup } from '../_shared/Popup'
|
||||
import { VotersList } from '../_shared/VotersList'
|
||||
import stylesComment from './CommentRatingControl.module.scss'
|
||||
import stylesShout from './ShoutRatingControl.module.scss'
|
||||
|
||||
interface RatingControlProps {
|
||||
shout?: Shout
|
||||
comment?: Reaction
|
||||
class?: string
|
||||
}
|
||||
|
||||
export const RatingControl = (props: RatingControlProps) => {
|
||||
const { t, lang } = useLocalize()
|
||||
const [_, changeSearchParams] = useSearchParams()
|
||||
const snackbar = useSnackbar()
|
||||
const { session } = useSession()
|
||||
const {
|
||||
reactionEntities,
|
||||
reactionsByShout,
|
||||
createReaction,
|
||||
deleteReaction,
|
||||
loadShoutRatings,
|
||||
loadCommentRatings
|
||||
} = useReactions()
|
||||
const [myRate, setMyRate] = createSignal<Reaction | undefined>()
|
||||
const [ratingReactions, setRatingReactions] = createSignal<Reaction[]>([])
|
||||
const [isLoading, setIsLoading] = createSignal(false)
|
||||
|
||||
// reaction kind
|
||||
const checkReaction = (reactionKind: ReactionKind) =>
|
||||
Object.values(reactionEntities).some(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.created_by.slug === session()?.user?.app_data?.profile?.slug &&
|
||||
r.shout.id === props.comment?.shout.id &&
|
||||
r.reply_to === props.comment?.id
|
||||
)
|
||||
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
||||
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
|
||||
|
||||
createEffect(() => {
|
||||
const shout = props.comment?.shout.id || props.shout?.id
|
||||
if (shout && !ratingReactions()) {
|
||||
let result = Object.values(reactionEntities).filter(
|
||||
(r) => [ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) && r.shout.id === shout
|
||||
)
|
||||
if (props.comment?.id) result = result.filter((r) => r.reply_to === props.comment?.id)
|
||||
setRatingReactions(result)
|
||||
}
|
||||
})
|
||||
|
||||
const deleteRating = async (reactionKind: ReactionKind) => {
|
||||
const reactionToDelete = Object.values(reactionEntities).find(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.created_by.slug === session()?.user?.nickname &&
|
||||
r.shout.id === props.comment?.shout.id &&
|
||||
r.reply_to === props.comment?.id
|
||||
)
|
||||
return reactionToDelete && deleteReaction(reactionToDelete.id)
|
||||
}
|
||||
|
||||
// rating change
|
||||
const handleRatingChange = async (isUpvote: boolean) => {
|
||||
setIsLoading(true)
|
||||
let error = ''
|
||||
try {
|
||||
if (isUpvoted() && isUpvote) return
|
||||
if (isDownvoted() && !isUpvote) return
|
||||
if (isUpvoted() && !isUpvote) error = (await deleteRating(ReactionKind.Like))?.error || ''
|
||||
if (isDownvoted() && isUpvote) error = (await deleteRating(ReactionKind.Dislike))?.error || ''
|
||||
if (!(isUpvoted() || isDownvoted())) {
|
||||
props.comment?.shout.id &&
|
||||
(await createReaction({
|
||||
reaction: {
|
||||
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||
shout: props.comment.shout.id,
|
||||
reply_to: props.comment?.id
|
||||
}
|
||||
}))
|
||||
}
|
||||
} catch (err) {
|
||||
snackbar?.showSnackbar({ type: 'error', body: `${t('Error')}: ${error || err || ''}` })
|
||||
}
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
const total = createMemo<number>(() =>
|
||||
props.comment?.stat?.rating ? props.comment.stat.rating : props.shout?.stat?.rating || 0
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
[ratingReactions, () => session()?.user?.app_data?.profile],
|
||||
([reactions, me]) => {
|
||||
console.debug('[RatingControl] on reactions update')
|
||||
const ratingVotes = Object.values(reactions).filter((r) => !r.reply_to)
|
||||
setRatingReactions((_) => ratingVotes.sort(byCreated))
|
||||
const myReaction = reactions.find((r) => r.created_by.id === me?.id)
|
||||
setMyRate((_) => myReaction)
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
const getTrigger = createMemo(() => {
|
||||
return (
|
||||
<div
|
||||
class={clsx(stylesComment.commentRatingValue, {
|
||||
[stylesComment.commentRatingPositive]: total() > 0 && Boolean(props.comment?.id),
|
||||
[stylesComment.commentRatingNegative]: total() < 0 && Boolean(props.comment?.id),
|
||||
[stylesShout.ratingValue]: !props.comment?.id
|
||||
})}
|
||||
>
|
||||
{total()}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
const VOTERS_PER_PAGE = 10
|
||||
const [ratingPage, setRatingPage] = createSignal(0)
|
||||
const [ratingLoading, setRatingLoading] = createSignal(false) // FIXME: use loading indication
|
||||
const ratings = createMemo(() =>
|
||||
props.shout
|
||||
? reactionsByShout[props.shout?.slug]?.filter(
|
||||
(r) => r.kind === ReactionKind.Like || r.kind === ReactionKind.Dislike
|
||||
)
|
||||
: []
|
||||
)
|
||||
const loadMoreReactions = async () => {
|
||||
if (!(props.shout?.id || props.comment?.id)) return [] as LoadMoreItems
|
||||
setRatingLoading(true)
|
||||
const next = ratingPage() + 1
|
||||
const offset = RATINGS_PER_PAGE * next
|
||||
const loader = props.comment ? loadCommentRatings : loadShoutRatings
|
||||
const rrr = await loader(props.shout?.id || 0, RATINGS_PER_PAGE, offset)
|
||||
rrr && setRatingPage(next)
|
||||
setRatingLoading(false)
|
||||
return rrr as LoadMoreItems
|
||||
}
|
||||
return props.comment?.id ? (
|
||||
<div class={stylesComment.commentRating}>
|
||||
<button
|
||||
role="button"
|
||||
disabled={!session()?.user?.app_data?.profile}
|
||||
onClick={() => handleRatingChange(true)}
|
||||
class={clsx(stylesComment.commentRatingControl, stylesComment.commentRatingControlUp, {
|
||||
[stylesComment.voted]: isUpvoted()
|
||||
})}
|
||||
/>
|
||||
<Popup
|
||||
trigger={
|
||||
<div
|
||||
class={clsx(stylesComment.commentRatingValue, {
|
||||
[stylesComment.commentRatingPositive]: (props.comment?.stat?.rating || 0) > 0,
|
||||
[stylesComment.commentRatingNegative]: (props.comment?.stat?.rating || 0) < 0
|
||||
})}
|
||||
>
|
||||
{props.comment?.stat?.rating || 0}
|
||||
</div>
|
||||
}
|
||||
variant="tiny"
|
||||
>
|
||||
<Show when={ratingLoading()}>
|
||||
<InlineLoader />
|
||||
</Show>
|
||||
<LoadMoreWrapper
|
||||
loadFunction={loadMoreReactions}
|
||||
pageSize={VOTERS_PER_PAGE}
|
||||
hidden={ratingLoading()}
|
||||
>
|
||||
<VotersList reactions={ratings()} fallbackMessage={t('This comment has not been rated yet')} />
|
||||
</LoadMoreWrapper>
|
||||
</Popup>
|
||||
<button
|
||||
role="button"
|
||||
disabled={!session()?.user?.app_data?.profile}
|
||||
onClick={() => handleRatingChange(false)}
|
||||
class={clsx(stylesComment.commentRatingControl, stylesComment.commentRatingControlDown, {
|
||||
[stylesComment.voted]: isDownvoted()
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div class={clsx(props.comment ? stylesComment.commentRating : stylesShout.rating, props.class)}>
|
||||
<button
|
||||
onClick={() => handleRatingChange(false)}
|
||||
disabled={isLoading()}
|
||||
class={
|
||||
props.comment
|
||||
? clsx(stylesComment.commentRatingControl, stylesComment.commentRatingControlUp, {
|
||||
[stylesComment.voted]: myRate()?.kind === 'LIKE'
|
||||
})
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Show when={!props.comment}>
|
||||
<Icon
|
||||
name={isDownvoted() ? 'rating-control-checked' : 'rating-control-less'}
|
||||
class={isLoading() ? 'rotating' : ''}
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
<Popup trigger={getTrigger()} variant="tiny">
|
||||
<Show
|
||||
when={!!session()?.user?.app_data?.profile}
|
||||
fallback={
|
||||
<>
|
||||
<span class="link" onClick={() => changeSearchParams({ mode: 'login', m: 'auth' })}>
|
||||
{t('Enter')}
|
||||
</span>
|
||||
{lang() === 'ru' ? ', ' : ' '}
|
||||
{t('to see the voters')}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<VotersList
|
||||
reactions={ratingReactions()}
|
||||
fallbackMessage={isLoading() ? t('Loading') : t('No one rated yet')}
|
||||
/>
|
||||
</Show>
|
||||
</Popup>
|
||||
<button
|
||||
onClick={() => handleRatingChange(true)}
|
||||
disabled={isLoading()}
|
||||
class={
|
||||
props.comment
|
||||
? clsx(stylesComment.commentRatingControl, stylesComment.commentRatingControlDown, {
|
||||
[stylesComment.voted]: myRate()?.kind === 'DISLIKE'
|
||||
})
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Show when={!props.comment}>
|
||||
<Icon
|
||||
name={isUpvoted() ? 'rating-control-checked' : 'rating-control-more'}
|
||||
class={isLoading() ? 'rotating' : ''}
|
||||
/>
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
107
src/components/Article/ShoutRatingControl.tsx
Normal file
107
src/components/Article/ShoutRatingControl.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { Show, createMemo, createSignal } from 'solid-js'
|
||||
import { useFeed } from '~/context/feed'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useReactions } from '~/context/reactions'
|
||||
import { useSession } from '~/context/session'
|
||||
import type { Author } from '~/graphql/schema/core.gen'
|
||||
import { ReactionKind, Shout } from '~/graphql/schema/core.gen'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { Popup } from '../_shared/Popup'
|
||||
import { VotersList } from '../_shared/VotersList'
|
||||
|
||||
import styles from './ShoutRatingControl.module.scss'
|
||||
|
||||
interface ShoutRatingControlProps {
|
||||
shout: Shout
|
||||
class?: string
|
||||
}
|
||||
|
||||
export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||
const { t } = useLocalize()
|
||||
const { loadShout } = useFeed()
|
||||
const { requireAuthentication, session } = useSession()
|
||||
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
||||
const [isLoading, setIsLoading] = createSignal(false)
|
||||
|
||||
const checkReaction = (reactionKind: ReactionKind) =>
|
||||
Object.values(reactionEntities).some(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.created_by.id === author()?.id &&
|
||||
r.shout.id === props.shout.id &&
|
||||
!r.reply_to
|
||||
)
|
||||
|
||||
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
||||
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
|
||||
|
||||
const shoutRatingReactions = createMemo(() =>
|
||||
Object.values(reactionEntities).filter(
|
||||
(r) => ['LIKE', 'DISLIKE'].includes(r.kind) && r.shout.id === props.shout.id && !r.reply_to
|
||||
)
|
||||
)
|
||||
|
||||
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
|
||||
const reactionToDelete = Object.values(reactionEntities).find(
|
||||
(r) =>
|
||||
r.kind === reactionKind &&
|
||||
r.created_by.id === author()?.id &&
|
||||
r.shout.id === props.shout.id &&
|
||||
!r.reply_to
|
||||
)
|
||||
if (reactionToDelete) return deleteReaction(reactionToDelete.id)
|
||||
}
|
||||
|
||||
const handleRatingChange = (isUpvote: boolean) => {
|
||||
requireAuthentication(async () => {
|
||||
setIsLoading(true)
|
||||
if (isUpvoted()) {
|
||||
await deleteShoutReaction(ReactionKind.Like)
|
||||
} else if (isDownvoted()) {
|
||||
await deleteShoutReaction(ReactionKind.Dislike)
|
||||
} else {
|
||||
await createReaction({
|
||||
reaction: {
|
||||
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||
shout: props.shout.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadShout(props.shout.slug)
|
||||
loadReactionsBy({
|
||||
by: { shout: props.shout.slug }
|
||||
})
|
||||
|
||||
setIsLoading(false)
|
||||
}, 'vote')
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={clsx(styles.rating, props.class)}>
|
||||
<button onClick={() => handleRatingChange(false)} disabled={isLoading()}>
|
||||
<Show when={!isDownvoted()} fallback={<Icon name="rating-control-checked" />}>
|
||||
<Icon name="rating-control-less" />
|
||||
</Show>
|
||||
</button>
|
||||
|
||||
<Popup
|
||||
trigger={<span class={styles.ratingValue}>{props.shout.stat?.rating || 0}</span>}
|
||||
variant="tiny"
|
||||
>
|
||||
<VotersList
|
||||
reactions={shoutRatingReactions()}
|
||||
fallbackMessage={t('This post has not been rated yet')}
|
||||
/>
|
||||
</Popup>
|
||||
|
||||
<button onClick={() => handleRatingChange(true)} disabled={isLoading()}>
|
||||
<Show when={!isUpvoted()} fallback={<Icon name="rating-control-checked" />}>
|
||||
<Icon name="rating-control-more" />
|
||||
</Show>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -26,6 +26,7 @@ type Props = {
|
|||
inviteView?: boolean
|
||||
onInvite?: (id: number) => void
|
||||
selected?: boolean
|
||||
subscriptionsMode?: boolean
|
||||
}
|
||||
export const AuthorBadge = (props: Props) => {
|
||||
const { session, requireAuthentication } = useSession()
|
||||
|
@ -116,7 +117,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
<div class={clsx('text-truncate', styles.bio)} innerHTML={props.author.bio || ''} />
|
||||
</Match>
|
||||
</Switch>
|
||||
<Show when={props.author?.stat}>
|
||||
<Show when={props.author?.stat && !props.subscriptionsMode}>
|
||||
<div class={styles.bio}>
|
||||
<Show when={(props.author?.stat?.shouts || 0) > 0}>
|
||||
<div>{t('some posts', { count: props.author.stat?.shouts ?? 0 })}</div>
|
||||
|
|
|
@ -162,7 +162,7 @@ export const AuthorCard = (props: Props) => {
|
|||
<For each={authorSubs()}>
|
||||
{(subscription) =>
|
||||
'name' in subscription ? (
|
||||
<AuthorBadge author={subscription as Author} nameOnly={true} />
|
||||
<AuthorBadge author={subscription as Author} subscriptionsMode={true} />
|
||||
) : (
|
||||
<TopicBadge topic={subscription as Topic} subscriptionsMode={true} />
|
||||
)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Editor } from '@tiptap/core'
|
||||
import { Blockquote } from '@tiptap/extension-blockquote'
|
||||
import { Bold } from '@tiptap/extension-bold'
|
||||
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
|
||||
|
@ -11,7 +10,7 @@ import { Paragraph } from '@tiptap/extension-paragraph'
|
|||
import { Placeholder } from '@tiptap/extension-placeholder'
|
||||
import { Text } from '@tiptap/extension-text'
|
||||
import { clsx } from 'clsx'
|
||||
import { Show, Suspense, createEffect, createMemo, createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||
import { Portal } from 'solid-js/web'
|
||||
import {
|
||||
createEditorTransaction,
|
||||
|
@ -20,23 +19,26 @@ import {
|
|||
useEditorIsEmpty,
|
||||
useEditorIsFocused
|
||||
} from 'solid-tiptap'
|
||||
import { Modal } from '~/components/_shared/Modal'
|
||||
import { useUI } from '~/context/ui'
|
||||
|
||||
import { useEditorContext } from '~/context/editor'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { UploadedFile } from '~/types/upload'
|
||||
import { useEditorContext } from '../../context/editor'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { Button } from '../_shared/Button'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { Loading } from '../_shared/Loading'
|
||||
import { Modal } from '../_shared/Modal'
|
||||
import { Popover } from '../_shared/Popover'
|
||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||
import { LinkBubbleMenuModule } from './LinkBubbleMenu'
|
||||
import styles from './SimplifiedEditor.module.scss'
|
||||
import { TextBubbleMenu } from './TextBubbleMenu'
|
||||
import { UploadModalContent } from './UploadModalContent'
|
||||
import { Figcaption } from './extensions/Figcaption'
|
||||
import { Figure } from './extensions/Figure'
|
||||
|
||||
import { Editor } from '@tiptap/core'
|
||||
import { useUI } from '~/context/ui'
|
||||
import styles from './SimplifiedEditor.module.scss'
|
||||
|
||||
type Props = {
|
||||
placeholder: string
|
||||
initialContent?: string
|
||||
|
@ -65,87 +67,98 @@ type Props = {
|
|||
const DEFAULT_MAX_LENGTH = 400
|
||||
|
||||
const SimplifiedEditor = (props: Props) => {
|
||||
const maxLength = props.maxLength ?? DEFAULT_MAX_LENGTH
|
||||
let wrapperEditorElRef: HTMLElement | undefined
|
||||
let editorElRef: HTMLElement | undefined
|
||||
let textBubbleMenuRef: HTMLDivElement | undefined
|
||||
let linkBubbleMenuRef: HTMLDivElement | undefined
|
||||
const { showModal, hideModal } = useUI()
|
||||
const { t } = useLocalize()
|
||||
const { showModal, hideModal } = useUI()
|
||||
const [counter, setCounter] = createSignal<number>(0)
|
||||
const [shouldShowLinkBubbleMenu, setShouldShowLinkBubbleMenu] = createSignal(false)
|
||||
const isCancelButtonVisible = createMemo(() => props.isCancelButtonVisible !== false)
|
||||
const { setEditor, editor } = useEditorContext()
|
||||
const [editorElement, setEditorElement] = createSignal<HTMLDivElement>()
|
||||
const { editor, setEditor } = useEditorContext()
|
||||
|
||||
const maxLength = props.maxLength ?? DEFAULT_MAX_LENGTH
|
||||
let wrapperEditorElRef: HTMLElement | undefined
|
||||
let textBubbleMenuRef: HTMLDivElement | undefined
|
||||
let linkBubbleMenuRef: HTMLDivElement | undefined
|
||||
|
||||
const ImageFigure = Figure.extend({
|
||||
name: 'capturedImage',
|
||||
content: 'figcaption image'
|
||||
})
|
||||
createEffect(() => {
|
||||
const e = createTiptapEditor(() => ({
|
||||
element: editorElRef as HTMLElement,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: styles.simplifiedEditorField
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => editorElement(),
|
||||
(ee: HTMLDivElement | undefined) => {
|
||||
if (ee && textBubbleMenuRef && linkBubbleMenuRef) {
|
||||
const freshEditor = createTiptapEditor<HTMLElement>(() => ({
|
||||
element: ee,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: styles.simplifiedEditorField
|
||||
}
|
||||
},
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Bold,
|
||||
Italic,
|
||||
Link.extend({
|
||||
inclusive: false
|
||||
}).configure({
|
||||
autolink: true,
|
||||
openOnClick: false
|
||||
}),
|
||||
CharacterCount.configure({
|
||||
limit: props.noLimits ? null : maxLength
|
||||
}),
|
||||
Blockquote.configure({
|
||||
HTMLAttributes: {
|
||||
class: styles.blockQuote
|
||||
}
|
||||
}),
|
||||
BubbleMenu.configure({
|
||||
pluginKey: 'textBubbleMenu',
|
||||
element: textBubbleMenuRef,
|
||||
shouldShow: ({ view, state }) => {
|
||||
if (!props.onlyBubbleControls) return false
|
||||
const { selection } = state
|
||||
const { empty } = selection
|
||||
return view.hasFocus() && !empty
|
||||
}
|
||||
}),
|
||||
BubbleMenu.configure({
|
||||
pluginKey: 'linkBubbleMenu',
|
||||
element: linkBubbleMenuRef,
|
||||
shouldShow: ({ state }) => {
|
||||
const { selection } = state
|
||||
const { empty } = selection
|
||||
return !empty && shouldShowLinkBubbleMenu()
|
||||
},
|
||||
tippyOptions: {
|
||||
placement: 'bottom'
|
||||
}
|
||||
}),
|
||||
ImageFigure,
|
||||
Image,
|
||||
Figcaption,
|
||||
Placeholder.configure({
|
||||
emptyNodeClass: styles.emptyNode,
|
||||
placeholder: props.placeholder
|
||||
})
|
||||
],
|
||||
autofocus: props.autoFocus,
|
||||
content: props.initialContent || null
|
||||
}))
|
||||
const editorInstance = freshEditor()
|
||||
if (!editorInstance) return
|
||||
setEditor(editorInstance)
|
||||
}
|
||||
},
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Bold,
|
||||
Italic,
|
||||
Link.extend({
|
||||
inclusive: false
|
||||
}).configure({
|
||||
autolink: true,
|
||||
openOnClick: false
|
||||
}),
|
||||
CharacterCount.configure({
|
||||
limit: props.noLimits ? null : maxLength
|
||||
}),
|
||||
Blockquote.configure({
|
||||
HTMLAttributes: {
|
||||
class: styles.blockQuote
|
||||
}
|
||||
}),
|
||||
BubbleMenu.configure({
|
||||
pluginKey: 'textBubbleMenu',
|
||||
element: textBubbleMenuRef,
|
||||
shouldShow: ({ view, state }) => {
|
||||
if (!props.onlyBubbleControls) return false
|
||||
const { selection } = state
|
||||
const { empty } = selection
|
||||
return view.hasFocus() && !empty
|
||||
}
|
||||
}),
|
||||
BubbleMenu.configure({
|
||||
pluginKey: 'linkBubbleMenu',
|
||||
element: linkBubbleMenuRef,
|
||||
shouldShow: ({ state }) => {
|
||||
const { selection } = state
|
||||
const { empty } = selection
|
||||
return !empty && shouldShowLinkBubbleMenu()
|
||||
},
|
||||
tippyOptions: {
|
||||
placement: 'bottom'
|
||||
}
|
||||
}),
|
||||
ImageFigure,
|
||||
Image,
|
||||
Figcaption,
|
||||
Placeholder.configure({
|
||||
emptyNodeClass: styles.emptyNode,
|
||||
placeholder: props.placeholder
|
||||
})
|
||||
],
|
||||
autofocus: props.autoFocus,
|
||||
content: content ?? null
|
||||
}))
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
e() && setEditor(e() as Editor)
|
||||
})
|
||||
const content = props.initialContent
|
||||
const isEmpty = useEditorIsEmpty(() => editor())
|
||||
const isFocused = useEditorIsFocused(() => editor())
|
||||
|
||||
|
@ -198,7 +211,7 @@ const SimplifiedEditor = (props: Props) => {
|
|||
}
|
||||
if (props.resetToInitial) {
|
||||
editor()?.commands.clearContent(true)
|
||||
props.initialContent && editor()?.commands.setContent(props.initialContent)
|
||||
if (props.initialContent) editor()?.commands.setContent(props.initialContent)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -261,137 +274,133 @@ const SimplifiedEditor = (props: Props) => {
|
|||
|
||||
return (
|
||||
<ShowOnlyOnClient>
|
||||
<Suspense>
|
||||
<div
|
||||
ref={(el) => (wrapperEditorElRef = el)}
|
||||
class={clsx(styles.SimplifiedEditor, {
|
||||
[styles.smallHeight]: props.smallHeight,
|
||||
[styles.minimal]: props.variant === 'minimal',
|
||||
[styles.bordered]: props.variant === 'bordered',
|
||||
[styles.isFocused]: isFocused() || !isEmpty(),
|
||||
[styles.labelVisible]: props.label && counter() > 0
|
||||
})}
|
||||
>
|
||||
<Show when={props.maxLength && editor()}>
|
||||
<div class={styles.limit}>{maxLength - counter()}</div>
|
||||
</Show>
|
||||
<Show when={props.label && counter() > 0}>
|
||||
<div class={styles.label}>{props.label}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.maxHeight} fallback={<div ref={(el) => (editorElRef = el)} />}>
|
||||
<div style={maxHeightStyle} ref={(el) => (editorElRef = el)} />
|
||||
</Show>
|
||||
|
||||
<Show when={!props.onlyBubbleControls}>
|
||||
<div class={clsx(styles.controls, { [styles.alwaysVisible]: props.controlsAlwaysVisible })}>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Bold')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.actionButton, { [styles.active]: isBold() })}
|
||||
onClick={() => editor()?.chain().focus().toggleBold().run()}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Italic')}>
|
||||
<div
|
||||
ref={(el) => (wrapperEditorElRef = el)}
|
||||
class={clsx(styles.SimplifiedEditor, {
|
||||
[styles.smallHeight]: props.smallHeight,
|
||||
[styles.minimal]: props.variant === 'minimal',
|
||||
[styles.bordered]: props.variant === 'bordered',
|
||||
[styles.isFocused]: isFocused() || !isEmpty(),
|
||||
[styles.labelVisible]: props.label && counter() > 0
|
||||
})}
|
||||
>
|
||||
<Show when={props.maxLength && editor()}>
|
||||
<div class={styles.limit}>{maxLength - counter()}</div>
|
||||
</Show>
|
||||
<Show when={props.label && counter() > 0}>
|
||||
<div class={styles.label}>{props.label}</div>
|
||||
</Show>
|
||||
<div style={props.maxHeight ? maxHeightStyle : undefined} ref={setEditorElement} />
|
||||
<Show when={!props.onlyBubbleControls}>
|
||||
<div class={clsx(styles.controls, { [styles.alwaysVisible]: props.controlsAlwaysVisible })}>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Bold')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.actionButton, { [styles.active]: isBold() })}
|
||||
onClick={() => editor()?.chain().focus().toggleBold().run()}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Italic')}>
|
||||
{(triggerRef) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.actionButton, { [styles.active]: isItalic() })}
|
||||
onClick={() => editor()?.chain().focus().toggleItalic().run()}
|
||||
>
|
||||
<Icon name="editor-italic" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Add url')}>
|
||||
{(triggerRef) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
onClick={handleShowLinkBubble}
|
||||
class={clsx(styles.actionButton, { [styles.active]: isLink() })}
|
||||
>
|
||||
<Icon name="editor-link" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Show when={props.quoteEnabled}>
|
||||
<Popover content={t('Add blockquote')}>
|
||||
{(triggerRef) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.actionButton, { [styles.active]: isItalic() })}
|
||||
onClick={() => editor()?.chain().focus().toggleItalic().run()}
|
||||
onClick={() => editor()?.chain().focus().toggleBlockquote().run()}
|
||||
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
|
||||
>
|
||||
<Icon name="editor-italic" />
|
||||
<Icon name="editor-quote" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Add url')}>
|
||||
</Show>
|
||||
<Show when={props.imageEnabled}>
|
||||
<Popover content={t('Add image')}>
|
||||
{(triggerRef) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
onClick={handleShowLinkBubble}
|
||||
class={clsx(styles.actionButton, { [styles.active]: isLink() })}
|
||||
onClick={() => showModal('simplifiedEditorUploadImage')}
|
||||
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
|
||||
>
|
||||
<Icon name="editor-link" />
|
||||
<Icon name="editor-image-dd-full" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Show when={props.quoteEnabled}>
|
||||
<Popover content={t('Add blockquote')}>
|
||||
{(triggerRef) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
onClick={() => editor()?.chain().focus().toggleBlockquote().run()}
|
||||
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
|
||||
>
|
||||
<Icon name="editor-quote" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</Show>
|
||||
<Show when={props.imageEnabled}>
|
||||
<Popover content={t('Add image')}>
|
||||
{(triggerRef) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
onClick={() => showModal('simplifiedEditorUploadImage')}
|
||||
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
|
||||
>
|
||||
<Icon name="editor-image-dd-full" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={!props.onChange}>
|
||||
<div class={styles.buttons}>
|
||||
<Show when={isCancelButtonVisible()}>
|
||||
<Button value={t('Cancel')} variant="secondary" onClick={handleClear} />
|
||||
</Show>
|
||||
<Show when={!props.isPosting} fallback={<Loading />}>
|
||||
<Button
|
||||
value={props.submitButtonText ?? t('Send')}
|
||||
variant="primary"
|
||||
disabled={isEmpty()}
|
||||
onClick={() => props.onSubmit?.(html() || '')}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={props.imageEnabled}>
|
||||
<Portal>
|
||||
<Modal variant="narrow" name="simplifiedEditorUploadImage">
|
||||
<UploadModalContent onClose={(value) => value && renderImage(value)} />
|
||||
</Modal>
|
||||
</Portal>
|
||||
</Show>
|
||||
<Show when={!!editor()}>
|
||||
<Show when={props.onlyBubbleControls}>
|
||||
<TextBubbleMenu
|
||||
shouldShow={true}
|
||||
isCommonMarkup={true}
|
||||
editor={editor() as Editor}
|
||||
ref={(el) => (textBubbleMenuRef = el)}
|
||||
/>
|
||||
<Show when={!props.onChange}>
|
||||
<div class={styles.buttons}>
|
||||
<Show when={isCancelButtonVisible()}>
|
||||
<Button value={t('Cancel')} variant="secondary" onClick={handleClear} />
|
||||
</Show>
|
||||
<Show when={!props.isPosting} fallback={<Loading />}>
|
||||
<Button
|
||||
value={props.submitButtonText ?? t('Send')}
|
||||
variant="primary"
|
||||
disabled={isEmpty()}
|
||||
onClick={() => props.onSubmit?.(html() || '')}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<LinkBubbleMenuModule
|
||||
editor={editor() as Editor}
|
||||
ref={(el) => (linkBubbleMenuRef = el)}
|
||||
onClose={handleHideLinkBubble}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={props.imageEnabled}>
|
||||
<Portal>
|
||||
<Modal variant="narrow" name="simplifiedEditorUploadImage">
|
||||
<UploadModalContent
|
||||
onClose={(value) => {
|
||||
renderImage(value as UploadedFile)
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</Portal>
|
||||
</Show>
|
||||
<Show when={props.onlyBubbleControls}>
|
||||
<TextBubbleMenu
|
||||
shouldShow={true}
|
||||
isCommonMarkup={true}
|
||||
editor={editor() as Editor}
|
||||
ref={(el) => (textBubbleMenuRef = el)}
|
||||
/>
|
||||
</Show>
|
||||
<LinkBubbleMenuModule
|
||||
editor={editor() as Editor}
|
||||
ref={(el) => (linkBubbleMenuRef = el)}
|
||||
onClose={handleHideLinkBubble}
|
||||
/>
|
||||
</div>
|
||||
</ShowOnlyOnClient>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ import type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen'
|
|||
import { capitalize } from '~/utils/capitalize'
|
||||
import { descFromBody } from '~/utils/meta'
|
||||
import { CoverImage } from '../../Article/CoverImage'
|
||||
import { RatingControl as ShoutRatingControl } from '../../Article/RatingControl'
|
||||
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
|
||||
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
|
||||
import { AuthorLink } from '../../Author/AuthorLink'
|
||||
import stylesHeader from '../../HeaderNav/Header.module.scss'
|
||||
import { CardTopic } from '../CardTopic'
|
||||
|
|
|
@ -753,12 +753,7 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rightItem {
|
||||
margin-right: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
a:link,
|
||||
|
@ -801,13 +796,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.rightItemIcon {
|
||||
display: inline-block;
|
||||
margin-left: 0.3em;
|
||||
position: relative;
|
||||
top: 0.15em;
|
||||
}
|
||||
|
||||
.editorPopup {
|
||||
border: 1px solid rgb(0 0 0 / 15%) !important;
|
||||
border-radius: 1.6rem;
|
||||
|
|
|
@ -18,6 +18,8 @@ import stylesAuthorList from './AuthorsList.module.scss'
|
|||
|
||||
type Props = {
|
||||
authors: Author[]
|
||||
authorsByFollowers?: Author[]
|
||||
authorsByShouts?: Author[]
|
||||
isLoaded: boolean
|
||||
}
|
||||
|
||||
|
@ -33,43 +35,48 @@ export const AllAuthors = (props: Props) => {
|
|||
const [searchParams, changeSearchParams] = useSearchParams<{ by?: string }>()
|
||||
const { authorsSorted, setAuthorsSort, loadAuthors } = useAuthors()
|
||||
const [loading, setLoading] = createSignal<boolean>(false)
|
||||
const [searchQuery, setSearchQuery] = createSignal('')
|
||||
const [filteredAuthors, setFilteredAuthors] = createSignal<Author[]>([])
|
||||
const [_currentAuthors, setCurrentAuthors] = createSignal<Author[]>([])
|
||||
|
||||
// UPDATE Fetch authors initially and when searchParams.by changes
|
||||
createEffect(() => {
|
||||
// Load all authors initially
|
||||
fetchAuthors(searchParams.by || 'name', 0)
|
||||
})
|
||||
|
||||
/* const authors = createMemo(() => {
|
||||
let sortedAuthors = [...props.authors]
|
||||
sortedAuthors = authorsSorted()
|
||||
if (!searchParams.by || searchParams.by === 'name') {
|
||||
const authors = createMemo(() => {
|
||||
let sortedAuthors = [...(props.authors || authorsSorted())] // Clone the array to avoid mutating the original
|
||||
console.log('Before Sorting:', sortedAuthors.slice(0, 5)) // Log the first 5 authors for comparison
|
||||
if (searchParams.by === 'name') {
|
||||
sortedAuthors = sortedAuthors.sort(byFirstChar)
|
||||
console.log('Sorted by Name:', sortedAuthors.slice(0, 5))
|
||||
} else if (searchParams.by === 'shouts') {
|
||||
sortedAuthors = sortedAuthors.sort(byStat('shouts'))
|
||||
console.log('Sorted by Shouts:', sortedAuthors.slice(0, 5))
|
||||
} else if (searchParams.by === 'followers') {
|
||||
sortedAuthors = sortedAuthors.sort(byStat('followers'))
|
||||
console.log('Sorted by Followers:', sortedAuthors.slice(0, 5))
|
||||
}
|
||||
return sortedAuthors
|
||||
}) */
|
||||
|
||||
const authors = createMemo(() => {
|
||||
let sortedAuthors: Author[] = []
|
||||
if (!searchParams.by || searchParams.by === 'name') {
|
||||
sortedAuthors = [...props.authors].sort(byFirstChar)
|
||||
} else {
|
||||
sortedAuthors = authorsSorted().sort(byStat(searchParams.by || 'shouts'))
|
||||
}
|
||||
console.log('After Sorting:', sortedAuthors.slice(0, 5))
|
||||
return sortedAuthors
|
||||
})
|
||||
|
||||
// Log authors data and searchParams for debugging
|
||||
createEffect(() => {
|
||||
setFilteredAuthors(dummyFilter(authors(), searchQuery(), lang()) as Author[])
|
||||
console.log('Authors:', props.authors.slice(0, 5)) // Log the first 5 authors
|
||||
console.log('Sorted Authors:', authors().slice(0, 5)) // Log the first 5 sorted authors
|
||||
console.log('Search Params "by":', searchParams.by)
|
||||
})
|
||||
|
||||
// filter
|
||||
const [searchQuery, setSearchQuery] = createSignal('')
|
||||
const [filteredAuthors, setFilteredAuthors] = createSignal<Author[]>([])
|
||||
createEffect(
|
||||
() => authors() && setFilteredAuthors(dummyFilter(authors(), searchQuery(), lang()) as Author[])
|
||||
)
|
||||
|
||||
// store by first char
|
||||
const byLetterFiltered = createMemo<{ [letter: string]: Author[] }>(() => {
|
||||
if (!(filteredAuthors()?.length > 0)) return {}
|
||||
console.debug('[components.AllAuthors] update byLetterFiltered', filteredAuthors()?.length)
|
||||
return filteredAuthors().reduce(
|
||||
(acc, author: Author) => authorLetterReduce(acc, author, lang()),
|
||||
{} as { [letter: string]: Author[] }
|
||||
|
@ -86,6 +93,7 @@ export const AllAuthors = (props: Props) => {
|
|||
|
||||
const fetchAuthors = async (queryType: string, page: number) => {
|
||||
try {
|
||||
console.debug('[components.AuthorsList] fetching authors...')
|
||||
setLoading(true)
|
||||
setAuthorsSort?.(queryType)
|
||||
const offset = AUTHORS_PER_PAGE * page
|
||||
|
@ -94,6 +102,8 @@ export const AllAuthors = (props: Props) => {
|
|||
limit: AUTHORS_PER_PAGE,
|
||||
offset
|
||||
})
|
||||
// UPDATE authors to currentAuthors state
|
||||
setCurrentAuthors((prev) => [...prev, ...authorsSorted()])
|
||||
} catch (error) {
|
||||
console.error('[components.AuthorsList] error fetching authors:', error)
|
||||
} finally {
|
||||
|
@ -121,7 +131,7 @@ export const AllAuthors = (props: Props) => {
|
|||
<ul class={clsx(styles.viewSwitcher, 'view-switcher')}>
|
||||
<li
|
||||
class={clsx({
|
||||
['view-switcher__item--selected']: searchParams?.by === 'shouts'
|
||||
['view-switcher__item--selected']: !searchParams?.by || searchParams?.by === 'shouts'
|
||||
})}
|
||||
>
|
||||
<a href="#" onClick={() => changeSearchParams({ by: 'shouts' })}>
|
||||
|
@ -139,7 +149,7 @@ export const AllAuthors = (props: Props) => {
|
|||
</li>
|
||||
<li
|
||||
class={clsx({
|
||||
['view-switcher__item--selected']: !searchParams?.by || searchParams?.by === 'name'
|
||||
['view-switcher__item--selected']: searchParams?.by === 'name'
|
||||
})}
|
||||
>
|
||||
<a href="#" onClick={() => changeSearchParams({ by: 'name' })}>
|
||||
|
@ -237,13 +247,12 @@ export const AllAuthors = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show when={props.isLoaded} fallback={<Loading />}>
|
||||
<div class="offset-md-5">
|
||||
<TabNavigator />
|
||||
<Show when={!searchParams.by || searchParams.by === 'name'} fallback={<AuthorsSortedList />}>
|
||||
<Show when={searchParams?.by === 'name'} fallback={<AuthorsSortedList />}>
|
||||
<AbcNavigator />
|
||||
<AbcAuthorsList />
|
||||
</Show>
|
||||
|
|
|
@ -61,7 +61,7 @@ export const AuthorView = (props: AuthorViewProps) => {
|
|||
on(
|
||||
[() => session()?.user?.app_data?.profile, () => props.authorSlug || ''],
|
||||
async ([me, slug]) => {
|
||||
console.debug('[AuthorView] checking if my profile')
|
||||
console.debug('check if my profile')
|
||||
const my = slug && me?.slug === slug
|
||||
if (my) {
|
||||
console.debug('[Author] my profile precached')
|
||||
|
@ -86,7 +86,7 @@ export const AuthorView = (props: AuthorViewProps) => {
|
|||
() => authorsEntities()[props.author?.slug || props.authorSlug || ''],
|
||||
async (found) => {
|
||||
if (!found) return
|
||||
console.debug('[AuthorView] ')
|
||||
setAuthor(found)
|
||||
console.info(`[Author] profile for @${found.slug} fetched`)
|
||||
const followsResp = await query(getAuthorFollowsQuery, { slug: found.slug }).toPromise()
|
||||
const follows = followsResp?.data?.get_author_followers || {}
|
||||
|
@ -96,7 +96,6 @@ export const AuthorView = (props: AuthorViewProps) => {
|
|||
setFollowers(followersResp?.data?.get_author_followers || [])
|
||||
console.info(`[Author] followers for @${found.slug} fetched`)
|
||||
setIsFetching(false)
|
||||
setTimeout(() => setAuthor(found), 1)
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
|
@ -124,37 +123,7 @@ export const AuthorView = (props: AuthorViewProps) => {
|
|||
(tab) => tab && console.log('[views.Author] profile tab switched')
|
||||
)
|
||||
)
|
||||
const AuthorFeed = () => (
|
||||
<Show when={Array.isArray(props.shouts) && props.shouts.length > 0 && props.shouts[0]}>
|
||||
<Row1 article={props.shouts?.[0] as Shout} noauthor={true} nodate={true} />
|
||||
|
||||
<Show when={props.shouts?.length || 0}>
|
||||
<Show when={props.shouts?.length === 1}>
|
||||
<Row1 article={props.shouts?.[0] as Shout} noauthor={true} nodate={true} />
|
||||
</Show>
|
||||
<Show when={props.shouts?.length === 2}>
|
||||
<Row2 articles={props.shouts as Shout[]} isEqual={true} noauthor={true} nodate={true} />
|
||||
</Show>
|
||||
<Show when={props.shouts?.length === 3}>
|
||||
<Row3 articles={props.shouts as Shout[]} noauthor={true} nodate={true} />
|
||||
</Show>
|
||||
<Show when={props.shouts && props.shouts.length > 3}>
|
||||
<For each={pages()}>
|
||||
{(page) => (
|
||||
<>
|
||||
<Row1 article={page[0]} noauthor={true} nodate={true} />
|
||||
<Row2 articles={page.slice(1, 3)} isEqual={true} noauthor={true} />
|
||||
<Row1 article={page[3]} noauthor={true} nodate={true} />
|
||||
<Row2 articles={page.slice(4, 6)} isEqual={true} noauthor={true} />
|
||||
<Row1 article={page[6]} noauthor={true} nodate={true} />
|
||||
<Row2 articles={page.slice(7, 9)} isEqual={true} noauthor={true} />
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</Show>
|
||||
</Show>
|
||||
)
|
||||
return (
|
||||
<div class={styles.authorPage}>
|
||||
<div class="wide-container">
|
||||
|
@ -260,7 +229,34 @@ export const AuthorView = (props: AuthorViewProps) => {
|
|||
</div>
|
||||
</Show>
|
||||
|
||||
<AuthorFeed />
|
||||
<Show when={Array.isArray(props.shouts) && props.shouts.length > 0 && props.shouts[0]}>
|
||||
<Row1 article={props.shouts?.[0] as Shout} noauthor={true} nodate={true} />
|
||||
|
||||
<Show when={props.shouts && props.shouts.length > 1}>
|
||||
<Switch>
|
||||
<Match when={props.shouts && props.shouts.length === 2}>
|
||||
<Row2 articles={props.shouts as Shout[]} isEqual={true} noauthor={true} nodate={true} />
|
||||
</Match>
|
||||
<Match when={props.shouts && props.shouts.length === 3}>
|
||||
<Row3 articles={props.shouts as Shout[]} noauthor={true} nodate={true} />
|
||||
</Match>
|
||||
<Match when={props.shouts && props.shouts.length > 3}>
|
||||
<For each={pages()}>
|
||||
{(page) => (
|
||||
<>
|
||||
<Row1 article={page[0]} noauthor={true} nodate={true} />
|
||||
<Row2 articles={page.slice(1, 3)} isEqual={true} noauthor={true} />
|
||||
<Row1 article={page[3]} noauthor={true} nodate={true} />
|
||||
<Row2 articles={page.slice(4, 6)} isEqual={true} noauthor={true} />
|
||||
<Row1 article={page[6]} noauthor={true} nodate={true} />
|
||||
<Row2 articles={page.slice(7, 9)} isEqual={true} noauthor={true} />
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
</Show>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
|
|
|
@ -9,27 +9,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.invert {
|
||||
filter: invert(100%);
|
||||
}
|
||||
|
||||
.rotating {
|
||||
/* Define the keyframes for the animation */
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply the animation to the element */
|
||||
animation: rotate .7s ease-out infinite; /* Rotate infinitely over 2 seconds using a linear timing function */
|
||||
}
|
||||
|
||||
|
||||
.notificationsCounter {
|
||||
@include media-breakpoint-up(md) {
|
||||
left: 1.8rem;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { Link } from '@solidjs/meta'
|
||||
import type { JSX } from 'solid-js'
|
||||
|
||||
import { Link } from '@solidjs/meta'
|
||||
import { splitProps } from 'solid-js'
|
||||
|
||||
import { getImageUrl } from '~/lib/getThumbUrl'
|
||||
|
||||
type Props = JSX.ImgHTMLAttributes<HTMLImageElement> & {
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
|
|||
import { byCreated } from '~/lib/sort'
|
||||
import { SortFunction } from '~/types/common'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
|
||||
import { Loading } from './Loading'
|
||||
|
||||
export type LoadMoreItems = Shout[] | Author[] | Reaction[]
|
||||
|
||||
|
@ -53,12 +52,14 @@ export const LoadMoreWrapper = (props: LoadMoreProps) => {
|
|||
return (
|
||||
<>
|
||||
{props.children}
|
||||
<Show when={isLoading()}>
|
||||
<Loading />
|
||||
</Show>
|
||||
<Show when={isLoadMoreButtonVisible() && !props.hidden && !isLoading()}>
|
||||
<Show when={isLoadMoreButtonVisible() && !props.hidden}>
|
||||
<div class="load-more-container">
|
||||
<Button onClick={loadItems} value={t('Load more')} title={`${items().length} ${t('loaded')}`} />
|
||||
<Button
|
||||
onClick={loadItems}
|
||||
disabled={isLoading()}
|
||||
value={t('Load more')}
|
||||
title={`${items().length} ${t('loaded')}`}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
|
|
|
@ -125,7 +125,7 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => {
|
|||
}))
|
||||
|
||||
// Определяем функцию сортировки по рейтингу
|
||||
const sortByRating: SortFunction<{ slug: string; rating: number }> = (a, b) => a.rating - b.rating
|
||||
const sortByRating: SortFunction<{ slug: string; rating: number }> = (a, b) => b.rating - a.rating
|
||||
|
||||
// Фильтруем и сортируем авторов
|
||||
const sortedTopAuthors = filterAndSort(authors, sortByRating)
|
||||
|
|
|
@ -2,12 +2,7 @@ import type { JSX } from 'solid-js'
|
|||
|
||||
import { createContext, onCleanup, useContext } from 'solid-js'
|
||||
import { createStore, reconcile } from 'solid-js/store'
|
||||
import {
|
||||
loadCommentRatings,
|
||||
loadReactions,
|
||||
loadShoutComments,
|
||||
loadShoutRatings
|
||||
} from '~/graphql/api/public'
|
||||
import { loadReactions } from '~/graphql/api/public'
|
||||
import createReactionMutation from '~/graphql/mutation/core/reaction-create'
|
||||
import destroyReactionMutation from '~/graphql/mutation/core/reaction-destroy'
|
||||
import updateReactionMutation from '~/graphql/mutation/core/reaction-update'
|
||||
|
@ -22,16 +17,10 @@ import { useGraphQL } from './graphql'
|
|||
import { useLocalize } from './localize'
|
||||
import { useSnackbar } from './ui'
|
||||
|
||||
export const COMMENTS_PER_PAGE = 50
|
||||
export const RATINGS_PER_PAGE = 100
|
||||
|
||||
type ReactionsContextType = {
|
||||
reactionEntities: Record<number, Reaction>
|
||||
reactionsByShout: Record<string, Reaction[]>
|
||||
loadReactionsBy: (args: QueryLoad_Reactions_ByArgs) => Promise<Reaction[]>
|
||||
loadShoutComments: (shout: number, limit?: number, offset?: number) => Promise<Reaction[]>
|
||||
loadShoutRatings: (shout: number, limit?: number, offset?: number) => Promise<Reaction[]>
|
||||
loadCommentRatings: (comment: number, limit?: number, offset?: number) => Promise<Reaction[]>
|
||||
createReaction: (reaction: MutationCreate_ReactionArgs) => Promise<void>
|
||||
updateReaction: (reaction: MutationUpdate_ReactionArgs) => Promise<Reaction>
|
||||
deleteReaction: (id: number) => Promise<{ error: string } | null>
|
||||
|
@ -74,42 +63,6 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
|||
return result
|
||||
}
|
||||
|
||||
const loadShoutRatingsAdding = async (
|
||||
shout: number,
|
||||
limit = RATINGS_PER_PAGE,
|
||||
offset = 0
|
||||
): Promise<Reaction[]> => {
|
||||
const fetcher = await loadShoutRatings({ shout, limit, offset })
|
||||
const result = (await fetcher()) || []
|
||||
console.debug('[context.reactions] shout ratings loaded', result)
|
||||
result && addReactions(result)
|
||||
return result
|
||||
}
|
||||
|
||||
const loadCommentRatingsAdding = async (
|
||||
comment: number,
|
||||
limit = RATINGS_PER_PAGE,
|
||||
offset = 0
|
||||
): Promise<Reaction[]> => {
|
||||
const fetcher = await loadCommentRatings({ comment, limit, offset })
|
||||
const result = (await fetcher()) || []
|
||||
console.debug('[context.reactions] shout ratings loaded', result)
|
||||
result && addReactions(result)
|
||||
return result
|
||||
}
|
||||
|
||||
const loadShoutCommentsAdding = async (
|
||||
shout: number,
|
||||
limit = COMMENTS_PER_PAGE,
|
||||
offset = 0
|
||||
): Promise<Reaction[]> => {
|
||||
const fetcher = await loadShoutComments({ shout, limit, offset })
|
||||
const result = (await fetcher()) || []
|
||||
console.debug('[context.reactions] shout comments loaded', result)
|
||||
result && addReactions(result)
|
||||
return result
|
||||
}
|
||||
|
||||
const createReaction = async (input: MutationCreate_ReactionArgs): Promise<void> => {
|
||||
const resp = await mutation(createReactionMutation, input).toPromise()
|
||||
const { error, reaction } = resp?.data?.create_reaction || {}
|
||||
|
@ -169,9 +122,6 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
|||
|
||||
const actions = {
|
||||
loadReactionsBy,
|
||||
loadShoutComments: loadShoutCommentsAdding,
|
||||
loadShoutRatings: loadShoutRatingsAdding,
|
||||
loadCommentRatings: loadCommentRatingsAdding,
|
||||
createReaction,
|
||||
updateReaction,
|
||||
deleteReaction,
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { cache } from '@solidjs/router'
|
||||
import { defaultClient } from '~/context/graphql'
|
||||
import loadShoutCommentsQuery from '~/graphql/query/core/article-comments-load'
|
||||
import getShoutQuery from '~/graphql/query/core/article-load'
|
||||
import loadShoutRatingsQuery from '~/graphql/query/core/article-ratings-load'
|
||||
import loadShoutsByQuery from '~/graphql/query/core/articles-load-by'
|
||||
import loadShoutsSearchQuery from '~/graphql/query/core/articles-load-search'
|
||||
import getAuthorQuery from '~/graphql/query/core/author-by'
|
||||
import loadAuthorsAllQuery from '~/graphql/query/core/authors-all'
|
||||
import loadAuthorsByQuery from '~/graphql/query/core/authors-load-by'
|
||||
import loadCommentRatingsQuery from '~/graphql/query/core/comment-ratings-load'
|
||||
import loadReactionsByQuery from '~/graphql/query/core/reactions-load-by'
|
||||
import loadFollowersByTopicQuery from '~/graphql/query/core/topic-followers'
|
||||
import loadTopicsQuery from '~/graphql/query/core/topics-all'
|
||||
|
@ -19,7 +16,6 @@ import {
|
|||
QueryGet_ShoutArgs,
|
||||
QueryLoad_Authors_ByArgs,
|
||||
QueryLoad_Reactions_ByArgs,
|
||||
QueryLoad_Shout_RatingsArgs,
|
||||
QueryLoad_Shouts_SearchArgs,
|
||||
Reaction,
|
||||
Shout,
|
||||
|
@ -61,39 +57,16 @@ export const loadShouts = (options: LoadShoutsOptions) => {
|
|||
}, `shouts-${filter}-${page}`)
|
||||
}
|
||||
|
||||
export const loadShoutComments = (options: QueryLoad_Shout_RatingsArgs) => {
|
||||
const page = `${options.offset || 0}-${(options.limit || 1) + (options.offset || 0)}`
|
||||
return cache(async () => {
|
||||
const resp = await defaultClient.query(loadShoutCommentsQuery, options).toPromise()
|
||||
const result = resp?.data?.load_reactions_by
|
||||
if (result) return result as Reaction[]
|
||||
}, `shout-${options.shout}-comments-${page}`)
|
||||
}
|
||||
|
||||
export const loadShoutRatings = (options: QueryLoad_Shout_RatingsArgs) => {
|
||||
const page = `${options.offset || 0}-${(options.limit || 1) + (options.offset || 0)}`
|
||||
return cache(async () => {
|
||||
const resp = await defaultClient.query(loadShoutRatingsQuery, options).toPromise()
|
||||
const result = resp?.data?.load_reactions_by
|
||||
if (result) return result as Reaction[]
|
||||
}, `shout-${options.shout}-ratings-${page}`)
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: FIXME: wait backend
|
||||
export const loadCommentRatings = (options: any) => {
|
||||
const page = `${options.offset || 0}-${(options.limit || 1) + (options.offset || 0)}`
|
||||
return cache(async () => {
|
||||
const resp = await defaultClient.query(loadCommentRatingsQuery, options).toPromise()
|
||||
const result = resp?.data?.load_reactions_by
|
||||
if (result) return result as Reaction[]
|
||||
}, `comment-${options.comment}-ratings-${page}`)
|
||||
}
|
||||
|
||||
export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => {
|
||||
if (!options.by) {
|
||||
console.debug(options)
|
||||
throw new Error('[api] wrong loadReactions call')
|
||||
}
|
||||
const kind = options.by?.comment ? 'comments' : options.by?.rating ? 'votes' : 'reactions'
|
||||
const allorone = options.by?.shout ? `shout-${options.by.shout}` : 'all'
|
||||
const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}`
|
||||
const filter = new URLSearchParams(options.by as Record<string, string>)
|
||||
// console.debug(options)
|
||||
return cache(async () => {
|
||||
const resp = await defaultClient.query(loadReactionsByQuery, options).toPromise()
|
||||
const result = resp?.data?.load_reactions_by
|
||||
|
@ -102,6 +75,7 @@ export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => {
|
|||
}
|
||||
|
||||
export const getShout = (options: QueryGet_ShoutArgs) => {
|
||||
// console.debug('[lib.api] get shout options', options)
|
||||
return cache(
|
||||
async () => {
|
||||
const resp = await defaultClient.query(getShoutQuery, { ...options }).toPromise()
|
||||
|
|
|
@ -18,7 +18,6 @@ export default gql`
|
|||
slug
|
||||
}
|
||||
created_by {
|
||||
id
|
||||
name
|
||||
slug
|
||||
pic
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
export default gql`
|
||||
mutation DeleteReactionMutation($reaction: Int!) {
|
||||
delete_reaction(reaction_id: $reaction) {
|
||||
mutation DeleteReactionMutation($reaction_id: Int!) {
|
||||
delete_reaction(reaction_id: $reaction_id) {
|
||||
error
|
||||
reaction {
|
||||
id
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
export default gql`
|
||||
query LoadReactions($shout: Int!, $limit: Int, $offset: Int) {
|
||||
load_shout_comments(shout: $shout, limit: $limit, offset: $offset) {
|
||||
id
|
||||
kind
|
||||
body
|
||||
reply_to
|
||||
shout {
|
||||
id
|
||||
slug
|
||||
title
|
||||
}
|
||||
created_by {
|
||||
id
|
||||
name
|
||||
slug
|
||||
pic
|
||||
created_at
|
||||
}
|
||||
created_at
|
||||
updated_at
|
||||
stat {
|
||||
rating
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,29 +0,0 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
export default gql`
|
||||
query LoadReactions($shout: Int!, $limit: Int, $offset: Int) {
|
||||
load_shout_ratings(shout: $shout, limit: $limit, offset: $offset) {
|
||||
id
|
||||
kind
|
||||
body
|
||||
reply_to
|
||||
shout {
|
||||
id
|
||||
slug
|
||||
title
|
||||
}
|
||||
created_by {
|
||||
id
|
||||
name
|
||||
slug
|
||||
pic
|
||||
created_at
|
||||
}
|
||||
created_at
|
||||
updated_at
|
||||
stat {
|
||||
rating
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,29 +0,0 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
export default gql`
|
||||
query LoadReactions($comment: Int!, $limit: Int, $offset: Int) {
|
||||
load_comment_ratings(comment: $comment, limit: $limit, offset: $offset) {
|
||||
id
|
||||
kind
|
||||
body
|
||||
reply_to
|
||||
shout {
|
||||
id
|
||||
slug
|
||||
title
|
||||
}
|
||||
created_by {
|
||||
id
|
||||
name
|
||||
slug
|
||||
pic
|
||||
created_at
|
||||
}
|
||||
created_at
|
||||
updated_at
|
||||
stat {
|
||||
rating
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -13,7 +13,6 @@ export default gql`
|
|||
title
|
||||
}
|
||||
created_by {
|
||||
id
|
||||
name
|
||||
slug
|
||||
pic
|
||||
|
|
|
@ -456,7 +456,7 @@
|
|||
"Theory": "Теории",
|
||||
"There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "В настройках вашего профиля есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?",
|
||||
"There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "В настройках публикации есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?",
|
||||
"This comment has not been rated yet": "Этот комментарий еще пока никто не оценил",
|
||||
"This comment has not yet been rated": "Этот комментарий еще пока никто не оценил",
|
||||
"This content is not published yet": "Содержимое ещё не опубликовано",
|
||||
"This email is": "Этот email",
|
||||
"This email is not verified": "Этот email не подтвержден",
|
||||
|
|
|
@ -34,7 +34,7 @@ export const byStat = (metric: string) => {
|
|||
return (a: { stat?: SomeStat }, b: { stat?: SomeStat }) => {
|
||||
const aStat = a.stat?.[metric] ?? 0
|
||||
const bStat = b.stat?.[metric] ?? 0
|
||||
return bStat - aStat
|
||||
return aStat - bStat
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,38 @@
|
|||
import { RouteDefinition, RoutePreloadFuncArgs, type RouteSectionProps, createAsync } from '@solidjs/router'
|
||||
import { RouteDefinition, RouteLoadFuncArgs, type RouteSectionProps, createAsync } from '@solidjs/router'
|
||||
import { Suspense, createEffect, on } from 'solid-js'
|
||||
import { AllAuthors } from '~/components/Views/AllAuthors'
|
||||
import { AUTHORS_PER_PAGE } from '~/components/Views/AllAuthors/AllAuthors'
|
||||
import { Loading } from '~/components/_shared/Loading'
|
||||
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||
import { useAuthors } from '~/context/authors'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { loadAuthorsAll } from '~/graphql/api/public'
|
||||
import { Author } from '~/graphql/schema/core.gen'
|
||||
import { loadAuthors, loadAuthorsAll } from '~/graphql/api/public'
|
||||
import { Author, AuthorsBy } from '~/graphql/schema/core.gen'
|
||||
|
||||
const fetchAuthorsWithStat = async (offset = 0, order?: string) => {
|
||||
const by: AuthorsBy = { order }
|
||||
const authorsFetcher = loadAuthors({ by, offset, limit: AUTHORS_PER_PAGE })
|
||||
return await authorsFetcher()
|
||||
}
|
||||
|
||||
// Fetch Function
|
||||
const fetchAllAuthors = async () => {
|
||||
const authorsAllFetcher = loadAuthorsAll()
|
||||
return await authorsAllFetcher()
|
||||
}
|
||||
|
||||
//Route Defenition
|
||||
export const route = {
|
||||
load: async ({ location: { query: _q } }: RoutePreloadFuncArgs) => {
|
||||
load: async ({ location: { query } }: RouteLoadFuncArgs) => {
|
||||
const by = query.by
|
||||
const isAll = !by || by === 'name'
|
||||
return {
|
||||
authors: await fetchAllAuthors()
|
||||
}
|
||||
authors: isAll && (await fetchAllAuthors()),
|
||||
authorsByFollowers: await fetchAuthorsWithStat(10, 'followers'),
|
||||
authorsByShouts: await fetchAuthorsWithStat(10, 'shouts')
|
||||
} as AllAuthorsData
|
||||
}
|
||||
} satisfies RouteDefinition
|
||||
|
||||
type AllAuthorsData = { authors: Author[] }
|
||||
type AllAuthorsData = { authors: Author[]; authorsByFollowers: Author[]; authorsByShouts: Author[] }
|
||||
|
||||
// addAuthors to context
|
||||
|
||||
|
@ -31,20 +40,25 @@ export default function AllAuthorsPage(props: RouteSectionProps<AllAuthorsData>)
|
|||
const { t } = useLocalize()
|
||||
const { addAuthors } = useAuthors()
|
||||
|
||||
// async load data: from ssr or fetch
|
||||
const data = createAsync<AllAuthorsData>(async () => {
|
||||
if (props.data) return props.data
|
||||
const authors = await fetchAllAuthors()
|
||||
return {
|
||||
authors: authors || []
|
||||
}
|
||||
authors: await fetchAllAuthors(),
|
||||
authorsByFollowers: await fetchAuthorsWithStat(10, 'followers'),
|
||||
authorsByShouts: await fetchAuthorsWithStat(10, 'shouts')
|
||||
} as AllAuthorsData
|
||||
})
|
||||
|
||||
// update context when data is loaded
|
||||
createEffect(
|
||||
on(
|
||||
[data, () => addAuthors],
|
||||
([data, aa]) => {
|
||||
if (data && aa) {
|
||||
aa(data.authors as Author[])
|
||||
aa(data.authorsByFollowers as Author[])
|
||||
aa(data.authorsByShouts as Author[])
|
||||
console.debug('[routes.author] added all authors:', data.authors)
|
||||
}
|
||||
},
|
||||
|
@ -59,7 +73,12 @@ export default function AllAuthorsPage(props: RouteSectionProps<AllAuthorsData>)
|
|||
desc="List of authors of the open editorial community"
|
||||
>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<AllAuthors isLoaded={Boolean(data()?.authors)} authors={data()?.authors || []} />
|
||||
<AllAuthors
|
||||
isLoaded={Boolean(data()?.authors)}
|
||||
authors={data()?.authors || []}
|
||||
authorsByFollowers={data()?.authorsByFollowers}
|
||||
authorsByShouts={data()?.authorsByShouts}
|
||||
/>
|
||||
</Suspense>
|
||||
</PageLayout>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { RouteSectionProps, createAsync } from '@solidjs/router'
|
||||
import { ErrorBoundary, createEffect, createMemo } from 'solid-js'
|
||||
import { RouteSectionProps } from '@solidjs/router'
|
||||
import { ErrorBoundary, createEffect, createMemo, createSignal, on } from 'solid-js'
|
||||
import { AuthorView } from '~/components/Views/Author'
|
||||
import { FourOuFourView } from '~/components/Views/FourOuFour'
|
||||
import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
|
||||
|
@ -32,7 +32,6 @@ const fetchAllTopics = async () => {
|
|||
const fetchAuthor = async (slug: string) => {
|
||||
const authorFetcher = loadAuthors({ by: { slug }, limit: 1, offset: 0 } as QueryLoad_Authors_ByArgs)
|
||||
const aaa = await authorFetcher()
|
||||
console.debug(aaa)
|
||||
return aaa?.[0]
|
||||
}
|
||||
|
||||
|
@ -50,20 +49,11 @@ export const route = {
|
|||
export type AuthorPageProps = { articles?: Shout[]; author?: Author; topics?: Topic[] }
|
||||
|
||||
export default function AuthorPage(props: RouteSectionProps<AuthorPageProps>) {
|
||||
const { authorsEntities } = useAuthors()
|
||||
const { addFeed, feedByAuthor } = useFeed()
|
||||
const { addAuthor, authorsEntities } = useAuthors()
|
||||
const [author, setAuthor] = createSignal<Author | undefined>(undefined)
|
||||
|
||||
const { t } = useLocalize()
|
||||
const author = createAsync(
|
||||
async () =>
|
||||
props.data.author || authorsEntities()[props.params.slug] || (await fetchAuthor(props.params.slug))
|
||||
)
|
||||
const shoutsByAuthor = createMemo(() => feedByAuthor()[props.params.slug])
|
||||
const title = createMemo(() => `${author()?.name || ''}`)
|
||||
const cover = createMemo(() =>
|
||||
author()?.pic
|
||||
? getImageUrl(author()?.pic || '', { width: 1200 })
|
||||
: getImageUrl('production/image/logo_image.png')
|
||||
)
|
||||
|
||||
createEffect(() => {
|
||||
if (author()) {
|
||||
|
@ -76,7 +66,32 @@ export default function AuthorPage(props: RouteSectionProps<AuthorPageProps>) {
|
|||
}
|
||||
})
|
||||
|
||||
const cover = createMemo(() =>
|
||||
author()?.pic
|
||||
? getImageUrl(author()?.pic || '', { width: 1200 })
|
||||
: getImageUrl('production/image/logo_image.png')
|
||||
)
|
||||
|
||||
// author shouts
|
||||
const { addFeed, feedByAuthor } = useFeed()
|
||||
const shoutsByAuthor = createMemo(() => feedByAuthor()[props.params.slug])
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
[() => props.params.slug || '', author],
|
||||
async ([slug, profile]) => {
|
||||
if (!profile) {
|
||||
const loadedAuthor = authorsEntities()[slug] || (await fetchAuthor(slug))
|
||||
if (loadedAuthor) {
|
||||
addAuthor(loadedAuthor)
|
||||
setAuthor(loadedAuthor)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
const loadAuthorShoutsMore = async (offset: number) => {
|
||||
const loadedShouts = await fetchAuthorShouts(props.params.slug, offset)
|
||||
loadedShouts && addFeed(loadedShouts)
|
||||
|
|
|
@ -125,7 +125,7 @@ export default (props: RouteSectionProps<{ shouts: Shout[]; topics: Topic[] }>)
|
|||
key="feed"
|
||||
desc="Independent media project about culture, science, art and society with horizontal editing"
|
||||
>
|
||||
<LoadMoreWrapper loadFunction={loadMoreFeed} pageSize={AUTHORS_PER_PAGE} hidden={!feed()}>
|
||||
<LoadMoreWrapper loadFunction={loadMoreFeed} pageSize={AUTHORS_PER_PAGE}>
|
||||
<ReactionsProvider>
|
||||
<Feed shouts={feed() || (shouts() as Shout[])} order={order() as FeedProps['order']} />
|
||||
</ReactionsProvider>
|
||||
|
|
Loading…
Reference in New Issue
Block a user