diff --git a/Aptfile b/Aptfile index 5b75cbb4f4..e52c7db358 100644 --- a/Aptfile +++ b/Aptfile @@ -8,7 +8,6 @@ xsltproc # poppler-utils contains pdftoppm, which we use to import PDF graphics as web-friendy images -:repo:deb http://cz.archive.ubuntu.com/ubuntu bionic main universe poppler-utils # imagemagick is used to convert other images to web-friendly formats diff --git a/app.json b/app.json index 8d425c9321..7c077b0626 100644 --- a/app.json +++ b/app.json @@ -1,5 +1,6 @@ { "addons": ["cloudamqp:lemur"], + "stack": "heroku-24", "buildpacks": [ { "url": "https://github.com/heroku/heroku-buildpack-apt.git" }, { "url": "heroku/nodejs" } diff --git a/client/components/Editor/schemas/audio.ts b/client/components/Editor/schemas/audio.ts index f5a1b800dd..f61cdcf161 100644 --- a/client/components/Editor/schemas/audio.ts +++ b/client/components/Editor/schemas/audio.ts @@ -36,6 +36,7 @@ export default { size: Number(node.getAttribute('data-size')) || 50, align: node.getAttribute('data-align') || 'center', caption: node.firstChild.getAttribute('alt') || '', + hideLabel: node.getAttribute('data-hide-label') || '', }; }, }, @@ -49,6 +50,7 @@ export default { 'data-node-type': 'audio', 'data-size': node.attrs.size, 'data-align': node.attrs.align, + 'data-hide-label': node.attrs.hideLabel, }, [ 'audio', diff --git a/client/components/Editor/schemas/code.ts b/client/components/Editor/schemas/code.ts index b3d86c5893..c51da34b5c 100644 --- a/client/components/Editor/schemas/code.ts +++ b/client/components/Editor/schemas/code.ts @@ -27,9 +27,17 @@ const renderStaticCode = (node: Node): DOMOutputSpec => { if (parser) { const tree = parser.parse(node.textContent); const children = fromLezer(node.textContent, tree as unknown as Tree); - return ['pre', ['code', ...children]] as DOMOutputSpec; + return [ + 'pre', + { id: node.attrs.id, 'data-lang': node.attrs.lang }, + ['code', ...children], + ] as DOMOutputSpec; } - return ['pre', ['code', node.textContent]] as DOMOutputSpec; + return [ + 'pre', + { id: node.attrs.id, ...(node.attrs.lang && { 'data-lang': node.attrs.lang }) }, + ['code', node.textContent], + ] as DOMOutputSpec; }; const codeSchema: { [key: string]: NodeSpec } = { diff --git a/client/components/Editor/schemas/iframe.ts b/client/components/Editor/schemas/iframe.ts index b55f9c8c92..95b1ccbf9e 100644 --- a/client/components/Editor/schemas/iframe.ts +++ b/client/components/Editor/schemas/iframe.ts @@ -38,6 +38,7 @@ export default { height: Number(node.firstChild.getAttribute('height')) || 419, align: node.getAttribute('data-align') || 'center', caption: node.firstChild.getAttribute('alt') || '', + hideLabel: node.getAttribute('data-hide-label') || '', }; }, }, @@ -51,6 +52,7 @@ export default { 'data-node-type': 'iframe', 'data-size': node.attrs.size, 'data-align': node.attrs.align, + 'data-hide-label': node.attrs.hideLabel, }, [ 'iframe', diff --git a/client/components/Editor/schemas/image.ts b/client/components/Editor/schemas/image.ts index dc2eeb10bb..e4354c64fd 100644 --- a/client/components/Editor/schemas/image.ts +++ b/client/components/Editor/schemas/image.ts @@ -58,13 +58,15 @@ export default { size: Number(node.getAttribute('data-size')) || 50, align: node.getAttribute('data-align') || 'center', altText: node.getAttribute('data-alt-text') || '', + hideLabel: node.getAttribute('data-hide-label') || '', href: node.getAttribute('data-href') || null, }; }, }, ], toDOM: (node, { isStaticallyRendered } = { isStaticallyRendered: false }) => { - const { url, align, id, altText, caption, fullResolution, size, href } = node.attrs; + const { url, align, id, altText, caption, fullResolution, size, hideLabel, href } = + node.attrs; const width = align === 'breakout' ? 1920 : 800; const isResizeable = isResizeableFormat(url) && !fullResolution; @@ -92,6 +94,7 @@ export default { 'data-caption': caption, 'data-href': href, 'data-alt-text': altText, + 'data-hide-label': hideLabel, }, href ? [ diff --git a/client/components/Editor/schemas/video.ts b/client/components/Editor/schemas/video.ts index ec2d43a9d5..982939c2f3 100644 --- a/client/components/Editor/schemas/video.ts +++ b/client/components/Editor/schemas/video.ts @@ -39,6 +39,7 @@ export default { align: node.getAttribute('data-align') || 'center', caption: node.firstChild.getAttribute('alt') || '', loop: !!node.firstChild.getAttribute('loop'), + hideLabel: node.getAttribute('data-hide-label') || '', }; }, }, @@ -52,6 +53,7 @@ export default { 'data-node-type': 'video', 'data-size': node.attrs.size, 'data-align': node.attrs.align, + 'data-hide-label': node.attrs.hideLabel, }, [ 'video', diff --git a/client/components/Editor/utils/renderStatic.ts b/client/components/Editor/utils/renderStatic.ts index 1759e6f02d..6662c747f7 100644 --- a/client/components/Editor/utils/renderStatic.ts +++ b/client/components/Editor/utils/renderStatic.ts @@ -105,7 +105,7 @@ const fillHoleInSpec = (outputSpec, children, isMark) => { }; const wrapOutputSpecInMarks = (outputSpec, marks, schema) => { - return marks.reduce((child, mark) => { + return [...marks].reverse().reduce((child, mark) => { const { spec: markSpec } = schema.marks[mark.type]; return fillHoleInSpec(markSpec.toDOM(mark), [child], true); }, outputSpec); diff --git a/client/components/Footer/Footer.tsx b/client/components/Footer/Footer.tsx index 08e2d4b18a..c56ece1d10 100644 --- a/client/components/Footer/Footer.tsx +++ b/client/components/Footer/Footer.tsx @@ -33,7 +33,6 @@ const defaultProps = { type Props = OwnProps & typeof defaultProps; const basePubPubFooterLinks = [ - { id: '1', title: 'Create your community', href: '/community/create' }, { id: '2', title: 'Login', href: '/login' }, { id: '3', title: 'Signup', href: '/signup' }, { id: '4', title: 'Legal', href: '/legal' }, @@ -143,21 +142,17 @@ const Footer = (props: Props) => {
diff --git a/client/components/GlobalControls/GlobalControls.tsx b/client/components/GlobalControls/GlobalControls.tsx index cf2ddab065..f1633ad895 100644 --- a/client/components/GlobalControls/GlobalControls.tsx +++ b/client/components/GlobalControls/GlobalControls.tsx @@ -99,9 +99,10 @@ const GlobalControls = (props: Props) => { if (isBasePubPub) { return ( <> - - - + {renderSearch()} ); diff --git a/client/components/UserAutocomplete/UserAutocomplete.tsx b/client/components/UserAutocomplete/UserAutocomplete.tsx index 9d02149f44..c016174a83 100644 --- a/client/components/UserAutocomplete/UserAutocomplete.tsx +++ b/client/components/UserAutocomplete/UserAutocomplete.tsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { Classes, MenuItem, Position } from '@blueprintjs/core'; import { Suggest } from '@blueprintjs/select'; - +import debounce from 'debounce'; import { Avatar } from 'components'; import { apiFetch } from 'client/utils/apiFetch'; @@ -28,12 +28,22 @@ type Props = OwnProps & typeof defaultProps; class UserAutocomplete extends Component { static defaultProps = defaultProps; + runUserQuery = (queryValue) => { + apiFetch(`/api/search/users?q=${queryValue}`).then((result) => { + const { usedUserIds } = this.props; + this.setState({ + items: result.filter((item) => !usedUserIds.includes(item.id)), + }); + }); + }; + constructor(props: Props) { super(props); this.state = { items: [], queryValue: '', }; + this.runUserQuery = debounce(this.runUserQuery, 300); // @ts-expect-error ts-migrate(2339) FIXME: Property 'inputRef' does not exist on type 'UserAu... Remove this comment to see the full error message this.inputRef = undefined; this.handleSelect = this.handleSelect.bind(this); @@ -41,13 +51,8 @@ class UserAutocomplete extends Component { componentDidUpdate(_: Props, prevState: State) { const { queryValue } = this.state; - if (queryValue !== prevState.queryValue) { - apiFetch(`/api/search/users?q=${queryValue}`).then((result) => { - const { usedUserIds } = this.props; - this.setState({ - items: result.filter((item) => !usedUserIds.includes(item.id)), - }); - }); + if (queryValue !== prevState.queryValue && queryValue !== '') { + this.runUserQuery(queryValue); } } diff --git a/client/containers/About/About.tsx b/client/containers/About/About.tsx index dcb5c9b516..90adf8338f 100644 --- a/client/containers/About/About.tsx +++ b/client/containers/About/About.tsx @@ -19,10 +19,6 @@ const About = function () { from drafting documents, conducting peer review, and hosting entire journal and book websites to collecting and displaying reader feedback and analytics.

-

- You can get started now by{' '} - creating your community. -

Features, Benefits, and Tradeoffs

PubPub is an open-source, hosted, free-to-use content management system designed diff --git a/client/containers/CommunityCreate/CommunityCreate.tsx b/client/containers/CommunityCreate/CommunityCreate.tsx index 162ac5f380..7715a1ad9f 100644 --- a/client/containers/CommunityCreate/CommunityCreate.tsx +++ b/client/containers/CommunityCreate/CommunityCreate.tsx @@ -82,15 +82,16 @@ const CommunityCreate = () => {

Create Community

- PubPub and all its features are free to use.* If you value - PubPub, we ask you to consider supporting our work by becoming a member - of the Knowledge Futures Group.{' '} + PubPub is evolving, and we are currently only allowing new community + creation for existing users with an explicit short-term need. Learn more + by reading our announcement. If you are an existing user who needs to + create a community, please{' '} - Learn more + get in touch .

@@ -178,21 +179,6 @@ const CommunityCreate = () => { /> -

- * We limit DOI registrations to 10 per community per year, if - published via PubPub's Crossref membership. Once the limit is reached, - we ask that you become a{' '} - - KFG member - - , at any level, and allow us to pass on the Crossref fee of $1 per DOI - registered. For groups with their own Crossref membership, there is no - additional fee for creating or depositing DOIs. -

)} diff --git a/client/containers/DashboardOverview/CollectionOverview/PubSelect.tsx b/client/containers/DashboardOverview/CollectionOverview/PubSelect.tsx index 5ad3d5aa25..4e4c9b2b51 100644 --- a/client/containers/DashboardOverview/CollectionOverview/PubSelect.tsx +++ b/client/containers/DashboardOverview/CollectionOverview/PubSelect.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useDebounce } from 'use-debounce'; import { PubMenuItem, QueryListDropdown } from 'components'; import { PubWithCollections } from 'types'; @@ -16,6 +17,7 @@ type Props = { const PubSelect = (props: Props) => { const { children, onSelectPub, collectionId, usedPubIds } = props; const [searchTerm, setSearchTerm] = useState(''); + const [debouncedSearchTerm] = useDebounce(searchTerm, 200); const { allQueries: { isLoading }, @@ -23,7 +25,7 @@ const PubSelect = (props: Props) => { } = useManyPubs({ batchSize: 50, query: { - term: searchTerm, + term: debouncedSearchTerm, excludeCollectionIds: [collectionId], ordering: { field: 'updatedDate', direction: 'DESC' }, }, diff --git a/client/containers/Explore/Explore.tsx b/client/containers/Explore/Explore.tsx index dc51aee1c5..b0c231ec11 100644 --- a/client/containers/Explore/Explore.tsx +++ b/client/containers/Explore/Explore.tsx @@ -17,13 +17,10 @@ const Explore = (props: Props) => {

Explore communities

- PubPub hosts over 3,000 communities, with more added every day! Anyone - can create a free PubPub community at - any time; all you need is an account and some very basic community - details. Below are 40 PubPub communities that we think form a good - snapshot of what you can create with PubPub's spaces and features. We - hope they provide inspiration for your own spaces, designs, and - workflows. + PubPub hosts over 3,000 communities, with more added every day! Below + are 40 PubPub communities that we think form a good snapshot of what you + can create with PubPub's spaces and features. We hope they provide + inspiration for your own spaces, designs, and workflows.
diff --git a/client/containers/Landing/Landing.tsx b/client/containers/Landing/Landing.tsx index 9dd986af8f..b82bac6ebe 100644 --- a/client/containers/Landing/Landing.tsx +++ b/client/containers/Landing/Landing.tsx @@ -1,165 +1,9 @@ import React from 'react'; import { Classes } from '@blueprintjs/core'; -import { Icon } from 'components'; - require('./landing.scss'); -const features = [ - { - icon: 'badge', - title: 'DOI Support', - desc: 'Generate CrossRef DOIs for your documents in one click.', - }, - { - icon: 'shield', - title: 'Submissions & Review', - desc: 'Manage submissions and peer review directly on PubPub.', - }, - { - icon: 'comment', - title: 'Discussions & Annotations', - desc: 'Host public and private discussions with your readers and community, whether in your classroom or across the world.', - }, - { - icon: 'page-layout', - title: 'Easily Customizable Layouts', - desc: 'Create your custom site without writing a line of code.', - }, - { - icon: 'book', - title: 'Collection Metadata', - desc: 'Include article & collection-level metadata for easier organization of content and improved discovery.', - }, - { - icon: 'people', - title: 'Access Control', - desc: 'Allow anyone to access your content, or just the people you choose.', - }, - { - icon: 'grouped-bar-chart', - title: 'Impact Measurement', - desc: 'Learn about the people visiting your community with a full suite of privacy-respecting analytics.', - }, - { - icon: 'graph', - title: 'Content Connections', - desc: 'Add typed relationships — reviews, commentary, supplement, etc. — to your content and deposit them to Crossref.', - }, - { - icon: 'export', - title: 'Document Export', - desc: 'Export your work to PDF, Word, Markdown, LaTeX, JATS XML, and more.', - }, -] as const; - -const communities = [ - { - name: 'Harvard Data Science Review', - description: 'A microscopic, telescopic & kaleidoscopic view of data science.', - logo: '/static/landing/hdsr.png', - type: 'Journals', - category: 'Science', - link: 'https://hdsr.mitpress.mit.edu', - }, - { - name: 'Frankenbook', - description: - 'A collaborative, multimedia reading experiment with Mary Shelley’s classic novel.', - logo: '/static/landing/frankenbook.png', - type: 'Books', - category: 'Literature', - link: 'https://frankenbook.org', - }, - { - name: 'Collective Wisdom', - description: "The British Library's early access and community review site.", - logo: '/static/landing/collective.png', - type: 'Resources', - category: 'Reports', - link: 'https://britishlibrary.pubpub.org/', - }, - { - name: 'SERC Ethical Computing', - description: 'A series on the social and ethical responsibilities of computing.', - logo: '/static/landing/serc.png', - type: 'Resources', - category: 'Case studies', - link: 'https://mit-serc.pubpub.org', - }, - { - name: 'Contours Collaborations', - description: 'A series on the social and ethical responsibilities of computing.', - logo: '/static/landing/contours.png', - type: 'Exhibits', - category: 'Arts', - link: 'https://contours.pubpub.org', - }, - { - name: 'Fermentology', - description: 'On the culture, history, and novelty of fermented things.', - logo: '/static/landing/fermentology.png', - type: 'Learning Series', - category: 'Multimedia', - link: 'https://fermentology.pubpub.org/', - }, - { - name: 'Grad Journal of Food Studies', - description: - 'An international student-run and refereed platform dedicated to encouraging and promoting interdisciplinary food scholarship at the graduate level', - logo: '/static/landing/gafs.png', - type: 'Student Journals', - category: 'Interdisciplinary', - link: 'https://gradfoodstudies.pubpub.org/', - }, - { - name: 'Critical Distance', - description: 'A pandemic and games essay jam.', - logo: '/static/landing/pgej.png', - type: 'Conferences', - category: 'Sprint', - link: 'https://pandemics-and-games-essay-jam.pubpub.org/', - }, - { - name: 'MIT Press Open Architecture', - description: 'An open collection of out-of-print humanities books.', - logo: '/static/landing/arch.png', - type: 'Collections', - category: 'Open humanities', - link: 'https://mitp-arch.mitpress.mit.edu/', - }, -]; - const Landing = () => { - const featureGrid = features.map((feature) => { - return ( -
- -
-

{feature.title}

-

{feature.desc}

-
-
- ); - }); - - const communityGrid = communities.map((community) => { - return ( - // @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element[]; className: string; ke... Remove this comment to see the full error message -
-
- {community.type} / {community.category} -
-

- {community.name} -

- - {`Logo - -

{community.description}

-
- ); - }); return (