diff --git a/.github/scripts/verify-metrics.sh b/.github/scripts/verify-metrics.sh index a6f4fa1..1904db5 100755 --- a/.github/scripts/verify-metrics.sh +++ b/.github/scripts/verify-metrics.sh @@ -71,6 +71,7 @@ actual_php="$(find . -name '*.php' \ -not -path './tests/*' \ -not -path './packages/*' \ -not -path './scripts/*' \ + -not -path './playground/*' \ -not -path './output/*' \ -not -path './build/*' \ -print0 | xargs -0 wc -l | tail -1 | awk '{print $1}')" diff --git a/.wordpress-org/blueprints/blueprint.json b/.wordpress-org/blueprints/blueprint.json index 80d45b2..ff03e34 100644 --- a/.wordpress-org/blueprints/blueprint.json +++ b/.wordpress-org/blueprints/blueprint.json @@ -26,7 +26,7 @@ }, { "step": "runPHP", - "code": "\n

Welcome to Borges Bibliography Builder. Use the Bibliography block below, then paste one or more sample entries into the Paste / Import field (separate entries with a blank line).

\n\n\n\n
10.1145/3368089.3409742\n\n10.1038/s41586-020-2649-2\n\nPMID:26673779\n\nPMC3531190\n\narXiv:1706.03762\n\nISBN 978-0-14-032872-1\n\n@article{knuth1984literate,\n  author = {Knuth, Donald E.},\n  title = {Literate Programming},\n  journal = {The Computer Journal},\n  year = {1984},\n  volume = {27},\n  number = {2},\n  pages = {97--111},\n  doi = {10.1093/comjnl/27.2.97}\n}
\n\n\n\n

Tip: In the block sidebar, try changing citation style and export actions (CSL-JSON, BibTeX, RIS).

\n\n\n\nEOT;\n\nwp_update_post(\n\tarray(\n\t\t'ID' => 1,\n\t\t'post_title' => 'Borges Bibliography Playground Starter',\n\t\t'post_content' => $post_content,\n\t)\n);\n" + "code": "Welcome to Borges Bibliography Builder. This Playground runs the latest release. It is a sandbox: nothing here is kept after you close the tab.';\n$borges_demo_json = <<<'JSON'\n{\"title\":\"Borges Bibliography Builder demo\",\"sampleGroups\":[{\"title\":\"DOIs\",\"description\":\"A bare DOI, a doi: prefix, or a doi.org link. Metadata comes from Crossref.\",\"samples\":[{\"format\":\"doi\",\"input\":\"10.1038/s41586-020-2649-2\"},{\"format\":\"doi\",\"input\":\"doi:10.1038/nature14539\"},{\"format\":\"doi\",\"input\":\"https://doi.org/10.1145/3368089.3409742\"}]},{\"title\":\"PubMed and PubMed Central\",\"description\":\"A PMID needs its PMID: label. A PMCID starts with PMC, with or without a PMCID: label.\",\"samples\":[{\"format\":\"pmid\",\"input\":\"PMID:26673779\"},{\"format\":\"pmcid\",\"input\":\"PMC3531190\"}]},{\"title\":\"arXiv\",\"description\":\"An arXiv: ID (modern or legacy), an arxiv.org link, or an arXiv DOI.\",\"samples\":[{\"format\":\"arxiv\",\"input\":\"arXiv:1706.03762\"},{\"format\":\"arxiv\",\"input\":\"https://arxiv.org/abs/1810.04805\"},{\"format\":\"arxiv\",\"input\":\"arXiv:hep-th/9711200\"},{\"format\":\"arxiv\",\"input\":\"10.48550/arXiv.2005.14165\"}]},{\"title\":\"ISBNs\",\"description\":\"A labeled ISBN-13 or ISBN-10, or a bare ISBN-13 starting with 978 or 979. Books come from Open Library, with Google Books as a fallback.\",\"samples\":[{\"format\":\"isbn\",\"input\":\"ISBN 978-0-14-032872-1\"},{\"format\":\"isbn\",\"input\":\"9780262033848\"},{\"format\":\"isbn\",\"input\":\"ISBN-10: 0-8112-0012-4\"}]},{\"title\":\"BibTeX and BibLaTeX\",\"description\":\"Whole entries, one or many. BibLaTeX fields such as date and location are understood.\",\"samples\":[{\"format\":\"bibtex\",\"input\":\"@article{knuth1984literate,\\n author = {Knuth, Donald E.},\\n title = {Literate Programming},\\n journal = {The Computer Journal},\\n year = {1984},\\n volume = {27},\\n number = {2},\\n pages = {97--111},\\n doi = {10.1093/comjnl/27.2.97}\\n}\"},{\"format\":\"bibtex\",\"input\":\"@book{borges1962labyrinths,\\n author = {Borges, Jorge Luis},\\n title = {Labyrinths: Selected Stories and Other Writings},\\n editor = {Yates, Donald A. and Irby, James E.},\\n date = {1962},\\n publisher = {New Directions},\\n location = {New York}\\n}\"}]},{\"title\":\"Formatted citations\",\"description\":\"Plain-text citations for books, journal articles, chapters, and webpages, in Chicago or APA shape. A citation that contains a DOI, PMID, PMCID, or arXiv ID is looked up by that identifier instead.\",\"samples\":[{\"format\":\"freetext\",\"input\":\"Binder, Amy J., and Jeffrey L. Kidder. The Channels of Student Activism: How the Left and Right Are Winning (and Losing) in Campus Politics Today. University of Chicago Press, 2022.\"},{\"format\":\"freetext\",\"input\":\"Dittmar, Emily L., and Douglas W. Schemske. “Temporal Variation in Selection Influences Microgeographic Local Adaptation.” American Naturalist 202, no. 4 (2023): 471–85.\"},{\"format\":\"freetext\",\"input\":\"Doyle, Kathleen. “The Queen Mary Psalter.” In The Book by Design: The Remarkable Story of the World’s Greatest Invention, edited by P. J. M. Marks and Stephen Parkin. University of Chicago Press, 2023.\"},{\"format\":\"freetext\",\"input\":\"Yale University. “About Yale: Yale Facts.” Accessed March 8, 2022. https://www.yale.edu/about-yale/yale-facts.\"},{\"format\":\"doi\",\"embedded\":true,\"input\":\"Dittmar, Emily L., and Douglas W. Schemske. “Temporal Variation in Selection Influences Microgeographic Local Adaptation.” American Naturalist 202, no. 4 (2023): 471–85. https://doi.org/10.1086/725865.\"}]}],\"features\":[\"Citation Style (block sidebar): switch among Chicago, APA, MLA, Harvard, IEEE, Vancouver, OSCOLA, ABNT, and more. Generated text reformats; your manual edits are kept.\",\"Edit an entry: select a citation and use its edit button to correct fields. Reset edits restores the generated text.\",\"Manual Entry: next to Paste / Import, add a citation field by field when there is nothing to paste.\",\"Duplicates: paste a DOI that is already in the list; it is skipped, not added twice.\",\"Order: notes and author-date styles sort alphabetically. Numeric styles (IEEE, Vancouver) keep your order; use Move citation up/down.\",\"Exports (block toolbar): copy the bibliography as text, or download BibTeX, BibLaTeX, RIS, or CSL-JSON.\",\"Per-entry Cite / Export (sidebar): gives readers copy and download links under each entry on the published page.\",\"Machine-readable output (sidebar): JSON-LD for search engines (on by default), COinS for Zotero, and embedded CSL-JSON.\",\"Front end: use View Post to see the published bibliography, then open its read-only REST data.\"],\"examples\":[{\"title\":\"Chicago notes-bibliography, with Cite / Export\",\"description\":\"A book, a chapter, a journal article, and a webpage, sorted alphabetically. Per-entry Cite / Export and COinS are on; view the post to try the copy and download links.\",\"attributes\":{\"bibliographyId\":\"demo-chicago-notes\",\"citationStyle\":\"chicago-notes-bibliography\",\"headingText\":\"Works Cited\",\"outputJsonLd\":true,\"outputCoins\":true,\"outputCslJson\":false,\"outputCiteExport\":true},\"citations\":[{\"type\":\"book\",\"title\":\"Labyrinths: Selected Stories and Other Writings\",\"author\":[{\"family\":\"Borges\",\"given\":\"Jorge Luis\"}],\"editor\":[{\"family\":\"Yates\",\"given\":\"Donald A.\"},{\"family\":\"Irby\",\"given\":\"James E.\"}],\"publisher\":\"New Directions\",\"publisher-place\":\"New York\",\"issued\":{\"date-parts\":[[1962]]}},{\"type\":\"chapter\",\"title\":\"The Library of Babel\",\"author\":[{\"family\":\"Borges\",\"given\":\"Jorge Luis\"}],\"translator\":[{\"family\":\"Irby\",\"given\":\"James E.\"}],\"container-title\":\"Labyrinths: Selected Stories and Other Writings\",\"editor\":[{\"family\":\"Yates\",\"given\":\"Donald A.\"},{\"family\":\"Irby\",\"given\":\"James E.\"}],\"publisher\":\"New Directions\",\"publisher-place\":\"New York\",\"issued\":{\"date-parts\":[[1962]]}},{\"type\":\"article-journal\",\"title\":\"Literate Programming\",\"author\":[{\"family\":\"Knuth\",\"given\":\"Donald E.\"}],\"container-title\":\"The Computer Journal\",\"volume\":\"27\",\"issue\":\"2\",\"page\":\"97-111\",\"DOI\":\"10.1093/comjnl/27.2.97\",\"issued\":{\"date-parts\":[[1984]]}},{\"type\":\"webpage\",\"title\":\"Block Editor Handbook\",\"author\":[{\"literal\":\"WordPress.org\"}],\"container-title\":\"WordPress Developer Resources\",\"URL\":\"https://developer.wordpress.org/block-editor/\"}],\"exports\":[{\"exportBibtex\":\"@book{Borges1962Labyrinths,\\n\\taddress = {New York},\\n\\tauthor = {Borges, Jorge Luis},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tyear = {1962},\\n\\tpublisher = {New Directions},\\n\\ttitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@book{Borges1962Labyrinths,\\n\\tauthor = {Borges, Jorge Luis},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tdate = {1962},\\n\\tlocation = {New York},\\n\\tpublisher = {New Directions},\\n\\ttitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n}\\n\\n\\n\"},{\"exportBibtex\":\"@inbook{Borges1962Library,\\n\\taddress = {New York},\\n\\tauthor = {Borges, Jorge Luis},\\n\\tbooktitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tyear = {1962},\\n\\tpublisher = {New Directions},\\n\\ttitle = {The {Library} of {Babel}},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@inbook{Borges1962Library,\\n\\tauthor = {Borges, Jorge Luis},\\n\\tbooktitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tdate = {1962},\\n\\tlocation = {New York},\\n\\tpublisher = {New Directions},\\n\\ttitle = {The {Library} of {Babel}},\\n\\ttranslator = {Irby, James E.},\\n}\\n\\n\\n\"},{\"exportBibtex\":\"@article{Knuth1984Literate,\\n\\tauthor = {Knuth, Donald E.},\\n\\tjournal = {The Computer Journal},\\n\\tdoi = {10.1093/comjnl/27.2.97},\\n\\tnumber = {2},\\n\\tyear = {1984},\\n\\tpages = {97--111},\\n\\ttitle = {Literate {Programming}},\\n\\tvolume = {27},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@article{Knuth1984Literate,\\n\\tauthor = {Knuth, Donald E.},\\n\\tjournaltitle = {The Computer Journal},\\n\\tdoi = {10.1093/comjnl/27.2.97},\\n\\tnumber = {2},\\n\\tdate = {1984},\\n\\tpages = {97--111},\\n\\ttitle = {Literate {Programming}},\\n\\tvolume = {27},\\n}\\n\\n\\n\"},{\"exportBibtex\":\"@misc{WordPressBlock,\\n\\tauthor = {{WordPress.org}},\\n\\ttitle = {Block {Editor} {Handbook}},\\n\\turl = {https://developer.wordpress.org/block-editor/},\\n\\thowpublished = {https://developer.wordpress.org/block-editor/},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@online{WordPressBlock,\\n\\tauthor = {{WordPress.org}},\\n\\tbooktitle = {WordPress {Developer} {Resources}},\\n\\ttitle = {Block {Editor} {Handbook}},\\n\\turl = {https://developer.wordpress.org/block-editor/},\\n}\\n\\n\\n\"}]},{\"title\":\"APA 7 author-date\",\"description\":\"Journal articles and an arXiv preprint in APA style, with DOIs as links.\",\"attributes\":{\"bibliographyId\":\"demo-apa-7\",\"citationStyle\":\"apa-7\",\"headingText\":\"References\",\"outputJsonLd\":true,\"outputCoins\":false,\"outputCslJson\":false,\"outputCiteExport\":false},\"citations\":[{\"type\":\"article-journal\",\"title\":\"Deep learning\",\"author\":[{\"family\":\"LeCun\",\"given\":\"Yann\"},{\"family\":\"Bengio\",\"given\":\"Yoshua\"},{\"family\":\"Hinton\",\"given\":\"Geoffrey\"}],\"container-title\":\"Nature\",\"volume\":\"521\",\"issue\":\"7553\",\"page\":\"436-444\",\"DOI\":\"10.1038/nature14539\",\"issued\":{\"date-parts\":[[2015]]}},{\"type\":\"article\",\"title\":\"Attention is all you need\",\"author\":[{\"family\":\"Vaswani\",\"given\":\"Ashish\"},{\"family\":\"Shazeer\",\"given\":\"Noam\"},{\"family\":\"Parmar\",\"given\":\"Niki\"},{\"family\":\"Uszkoreit\",\"given\":\"Jakob\"},{\"family\":\"Jones\",\"given\":\"Llion\"},{\"family\":\"Gomez\",\"given\":\"Aidan N.\"},{\"family\":\"Kaiser\",\"given\":\"Łukasz\"},{\"family\":\"Polosukhin\",\"given\":\"Illia\"}],\"publisher\":\"arXiv\",\"number\":\"1706.03762\",\"DOI\":\"10.48550/arXiv.1706.03762\",\"issued\":{\"date-parts\":[[2017]]}},{\"type\":\"article-journal\",\"title\":\"Computing machinery and intelligence\",\"author\":[{\"family\":\"Turing\",\"given\":\"A. M.\"}],\"container-title\":\"Mind\",\"volume\":\"59\",\"issue\":\"236\",\"page\":\"433-460\",\"DOI\":\"10.1093/mind/LIX.236.433\",\"issued\":{\"date-parts\":[[1950]]}}]},{\"title\":\"IEEE numeric\",\"description\":\"Numbered entries keep the order you give them. Select the block and use Move citation up/down to renumber.\",\"attributes\":{\"bibliographyId\":\"demo-ieee\",\"citationStyle\":\"ieee\",\"headingText\":\"References\",\"outputJsonLd\":true,\"outputCoins\":false,\"outputCslJson\":false,\"outputCiteExport\":false},\"citations\":[{\"type\":\"article-journal\",\"title\":\"A Mathematical Theory of Communication\",\"author\":[{\"family\":\"Shannon\",\"given\":\"Claude E.\"}],\"container-title\":\"Bell System Technical Journal\",\"volume\":\"27\",\"issue\":\"3\",\"page\":\"379-423\",\"DOI\":\"10.1002/j.1538-7305.1948.tb01338.x\",\"issued\":{\"date-parts\":[[1948]]}},{\"type\":\"article-journal\",\"title\":\"Computing Machinery and Intelligence\",\"author\":[{\"family\":\"Turing\",\"given\":\"A. M.\"}],\"container-title\":\"Mind\",\"volume\":\"59\",\"issue\":\"236\",\"page\":\"433-460\",\"DOI\":\"10.1093/mind/LIX.236.433\",\"issued\":{\"date-parts\":[[1950]]}},{\"type\":\"article-journal\",\"title\":\"Literate Programming\",\"author\":[{\"family\":\"Knuth\",\"given\":\"Donald E.\"}],\"container-title\":\"The Computer Journal\",\"volume\":\"27\",\"issue\":\"2\",\"page\":\"97-111\",\"DOI\":\"10.1093/comjnl/27.2.97\",\"issued\":{\"date-parts\":[[1984]]}}]}]}\nJSON;\n/**\n * Playground demo page builder.\n *\n * `npm run playground:build` inlines this file into each demo blueprint's\n * runPHP step, after defining:\n *\n * - $borges_demo_json The contents of playground/demo-content.json.\n * - $borges_demo_intro An HTML lead paragraph for that blueprint.\n *\n * It rewrites post 1 as the demo page the blueprint lands on, and turns off\n * the editor's Welcome Guide so the page is visible straight away. Example\n * bibliographies are formatted with the plugin's own citeproc formatter and\n * rendered with its PHP port of save(), so they validate against whatever\n * version is installed. A release without that port (before 1.7.0) gets a\n * note instead.\n *\n * Not part of the plugin package (playground/ is in .distignore).\n *\n * @package BibliographyBuilder\n */\n\nrequire_once '/wordpress/wp-load.php';\n\n// Write as the admin, and keep kses from stripping the blocks' JSON-LD\n// scripts and data attributes: kses_init() ran before any user was set.\nwp_set_current_user( 1 );\nkses_remove_filters();\n\n$borges_demo = json_decode( $borges_demo_json, true );\n\n$borges_demo_block = static function ( $name, $attrs, $html ) {\n\treturn serialize_block(\n\t\tarray(\n\t\t\t'blockName' => $name,\n\t\t\t'attrs' => $attrs,\n\t\t\t'innerBlocks' => array(),\n\t\t\t'innerHTML' => $html,\n\t\t\t'innerContent' => array( $html ),\n\t\t)\n\t) . \"\\n\\n\";\n};\n\n$borges_demo_paragraph = static function ( $html ) use ( $borges_demo_block ) {\n\treturn $borges_demo_block( 'core/paragraph', array(), '

' . $html . '

' );\n};\n\n$borges_demo_heading = static function ( $text, $level ) use ( $borges_demo_block ) {\n\t$attrs = 2 === $level ? array() : array( 'level' => $level );\n\t$tag = 'h' . $level;\n\n\treturn $borges_demo_block( 'core/heading', $attrs, '<' . $tag . ' class=\"wp-block-heading\">' . esc_html( $text ) . '' );\n};\n\n$borges_demo_code = static function ( $text ) use ( $borges_demo_block ) {\n\treturn $borges_demo_block(\n\t\t'core/code',\n\t\tarray(),\n\t\t'
' . htmlspecialchars( $text, ENT_NOQUOTES, 'UTF-8' ) . '
'\n\t);\n};\n\n$borges_demo_list = static function ( $items ) {\n\t$html = \"\\n\\n\\n\\n\";\n};\n\n$borges_demo_can_prebuild = function_exists( 'bibliography_builder_render_save_markup' )\n\t&& function_exists( 'bibliography_builder_format_csl_items' )\n\t&& bibliography_builder_can_render_save_markup();\n\n// Returns the serialized block, or '' when this install cannot build it.\n$borges_demo_bibliography = static function ( $example ) use ( $borges_demo_can_prebuild ) {\n\tif ( ! $borges_demo_can_prebuild ) {\n\t\treturn '';\n\t}\n\n\t$csl_items = bibliography_builder_validate_and_sanitize_csl_items( $example['citations'] );\n\n\tif ( is_wp_error( $csl_items ) ) {\n\t\treturn '';\n\t}\n\n\t$formatted = bibliography_builder_format_csl_items( $csl_items, $example['attributes']['citationStyle'] );\n\n\tif ( is_wp_error( $formatted ) ) {\n\t\treturn '';\n\t}\n\n\t$citations = array();\n\n\tforeach ( array_values( $csl_items ) as $index => $csl ) {\n\t\t$citation = array(\n\t\t\t'id' => $example['attributes']['bibliographyId'] . '-' . ( $index + 1 ),\n\t\t\t'csl' => $csl,\n\t\t\t'formattedText' => $formatted[ $index ],\n\t\t);\n\n\t\t// The editor stores these for Cite / Export; without them it adds\n\t\t// them on load and the untouched demo opens with unsaved changes.\n\t\tforeach ( array( 'exportBibtex', 'exportBiblatex' ) as $export_key ) {\n\t\t\tif ( isset( $example['exports'][ $index ][ $export_key ] ) ) {\n\t\t\t\t$citation[ $export_key ] = $example['exports'][ $index ][ $export_key ];\n\t\t\t}\n\t\t}\n\n\t\t$citations[] = $citation;\n\t}\n\n\t$attrs = $example['attributes'];\n\t// Stored in display order, as the editor stores them.\n\t$attrs['citations'] = bibliography_builder_sort_citations_for_save( $citations, $attrs['citationStyle'] );\n\t// Render from the attributes as the editor will parse them back.\n\t$html = bibliography_builder_render_save_markup(\n\t\tbibliography_builder_decode_save_attributes( wp_json_encode( $attrs ) )\n\t);\n\n\tif ( '' === $html ) {\n\t\treturn '';\n\t}\n\n\treturn serialize_block(\n\t\tarray(\n\t\t\t'blockName' => 'bibliography-builder/bibliography',\n\t\t\t'attrs' => $attrs,\n\t\t\t'innerBlocks' => array(),\n\t\t\t'innerHTML' => $html,\n\t\t\t'innerContent' => array( $html ),\n\t\t)\n\t) . \"\\n\\n\";\n};\n\n$content = $borges_demo_paragraph( $borges_demo_intro );\n\n$content .= $borges_demo_heading( '1. Paste any supported format', 2 );\n$content .= $borges_demo_paragraph(\n\t'Select the empty Bibliography block at the end of this section, open Paste / Import, paste one or more samples, and choose Add. Separate entries with a blank line; up to 50 go in one paste, so you can copy several groups at once.'\n);\n\nforeach ( $borges_demo['sampleGroups'] as $group ) {\n\t$content .= $borges_demo_heading( $group['title'], 3 );\n\t$content .= $borges_demo_paragraph( $group['description'] );\n\t$content .= $borges_demo_code( implode( \"\\n\\n\", wp_list_pluck( $group['samples'], 'input' ) ) );\n}\n\n$content .= $borges_demo_block(\n\t'bibliography-builder/bibliography',\n\tarray(\n\t\t'bibliographyId' => 'demo-try-it',\n\t\t'headingText' => 'References',\n\t),\n\t''\n);\n\n$content .= $borges_demo_heading( '2. Key features to try', 2 );\n$content .= $borges_demo_list( $borges_demo['features'] );\n\n$content .= $borges_demo_heading( '3. Example bibliographies', 2 );\n\n$borges_demo_examples = '';\n\nforeach ( $borges_demo['examples'] as $example ) {\n\t$block = $borges_demo_bibliography( $example );\n\n\tif ( '' === $block ) {\n\t\tcontinue;\n\t}\n\n\t$borges_demo_examples .= $borges_demo_heading( $example['title'], 3 );\n\t$borges_demo_examples .= $borges_demo_paragraph( $example['description'] );\n\t$borges_demo_examples .= $block;\n}\n\n$content .= '' !== $borges_demo_examples\n\t? $borges_demo_paragraph( 'Each block below was built from CSL-JSON. Select one to change its style or settings.' ) . $borges_demo_examples\n\t: $borges_demo_paragraph( 'Pre-built examples need Borges 1.7.0 or later. Paste a few samples from section 1 to build your own.' );\n\nwp_update_post(\n\tarray(\n\t\t'ID' => 1,\n\t\t'post_title' => $borges_demo['title'],\n\t\t'post_content' => wp_slash( $content ),\n\t)\n);\n\n// Skip the block editor's Welcome Guide so the demo page is what opens.\n$borges_demo_meta_key = $GLOBALS['wpdb']->get_blog_prefix() . 'persisted_preferences';\n$borges_demo_preferences = get_user_meta( 1, $borges_demo_meta_key, true );\n$borges_demo_preferences = is_array( $borges_demo_preferences ) ? $borges_demo_preferences : array();\n\n$borges_demo_preferences['core/edit-post'] = array_merge(\n\tisset( $borges_demo_preferences['core/edit-post'] ) && is_array( $borges_demo_preferences['core/edit-post'] )\n\t\t? $borges_demo_preferences['core/edit-post']\n\t\t: array(),\n\tarray( 'welcomeGuide' => false )\n);\n$borges_demo_preferences['_modified'] = gmdate( 'c' );\n\nupdate_user_meta( 1, $borges_demo_meta_key, $borges_demo_preferences );\n" } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e36dbf..f72b6dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Three matching read-only abilities on WordPress 6.9+, with the same `edit_post` requirement: `borges/validate-bibliography`, `borges/find-duplicate-citations`, and `borges/preview-bibliography-style`. Each takes `post_id` and either `index` or `bibliography_id`. - Stable IDs (Phase 05, Tier 0). The read-only REST collection and single-bibliography routes, and the `borges/get-bibliographies` ability, now report each block's `bibliographyId`, a UUID that stays put when blocks before it are added or removed, as the future write routes will need. It is `null` for a block saved before IDs were assigned (or with an unusable value) until the post is next edited. Every citation already carried an `id`; the editor now guarantees one, unique within its block. +- The Playground demos (release, main build, and WordPress.org Preview) open on a demo page with copy-ready samples of every accepted input format, an empty Bibliography block to paste them into, brief notes on the key features, and three example bibliographies (Chicago notes-bibliography with Cite / Export, APA 7, IEEE). The Welcome Guide no longer covers the page. The page is defined once in `playground/demo-content.json` and `playground/demo-content.php`, and `npm run playground:build` writes it into every Blueprint. + ### Fixed - A bibliography block no longer opens as invalid ("Attempt Block Recovery") when an editor using a different language from the one who saved it opens the post. `save()` used to write the Cite / Export labels ("Cite / Export", "Copy citation", "Copied", "RIS", "CSL-JSON", "BibTeX", "BibLaTeX") and the "Link to publication" link label into post content in the saving editor's language. The editor checks saved markup against `save()` in the *current* editor's language, so the two did not match. Saved markup is now the same in every language: the panel labels are stored in English and translated for visitors on the server as the block renders (`includes/frontend-labels.php`, a `render_block` filter), so they are translated without JavaScript and the view script loads no `wp-i18n`, and a link whose citation has no title or container title carries no `aria-label`, so screen readers announce its visible URL. Existing posts still validate in any language, because the new deprecation reads the labels back from the saved markup. The exception is blocks saved in the oldest markup shapes (before the biblioentry role was removed) that contain a linked URL in a citation with no title or container title: those still validate only in the language they were saved in, as before. They switch to the new markup the next time they are saved. The PHP port of `save()` (`includes/save-markup.php`) matches. diff --git a/CLAUDE.md b/CLAUDE.md index bf8fb94..317a73f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -100,7 +100,7 @@ A Gutenberg block plugin that accepts DOI identifiers, PubMed/PMID records, BibT ### Playground -The Playground blueprint at `playground/blueprint.json` configures the GitHub/readme demo (released version). It installs the latest GitHub Release ZIP through the WordPress Playground CORS proxy, installs Block Accessibility Checks from WordPress.org, and requires both `"phpExtensionBundles": ["kitchen-sink"]` and `features.intl` for the CSL formatter. A second blueprint at `playground/blueprint-main.json` powers the README "Main build" badge: it installs the `main-preview` rolling pre-release ZIP that CI's `publish-main-preview` job refreshes on every push to `main` — after the full CI suite passes and only when the commit is still `main`'s tip (live Playground can't use `git:directory`, so a stable release asset is how main HEAD boots). The WordPress.org Preview blueprint at `.wordpress-org/blueprints/blueprint.json` is separate; WordPress.org installs Borges automatically there. The E2E tests (`npm run test:e2e:playground`) run against the GitHub/readme blueprint. +The Playground blueprint at `playground/blueprint.json` configures the GitHub/readme demo (released version). It installs the latest GitHub Release ZIP through the WordPress Playground CORS proxy, installs Block Accessibility Checks from WordPress.org, and requires both `"phpExtensionBundles": ["kitchen-sink"]` and `features.intl` for the CSL formatter. A second blueprint at `playground/blueprint-main.json` powers the README "Main build" badge: it installs the `main-preview` rolling pre-release ZIP that CI's `publish-main-preview` job refreshes on every push to `main` — after the full CI suite passes and only when the commit is still `main`'s tip (live Playground can't use `git:directory`, so a stable release asset is how main HEAD boots). The WordPress.org Preview blueprint at `.wordpress-org/blueprints/blueprint.json` is separate; WordPress.org installs Borges automatically there. All three open post 1 as a demo page that is generated, not hand-edited: edit `playground/demo-content.json` / `demo-content.php`, then run `npm run playground:build` (`src/playground-demo.test.js` fails on stale blueprints). The E2E tests (`npm run test:e2e:playground`) run against the GitHub/readme blueprint. ### Release diff --git a/README.md b/README.md index 531bee4..a012a06 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,12 @@ Install the public release from [WordPress.org](https://wordpress.org/plugins/bo - **[Try the released version](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/dknauss/Borges/main/playground/blueprint.json)** — installs the latest GitHub Release ZIP (the same build published to WordPress.org) through the WordPress Playground CORS proxy. Use this to try the current stable plugin. - **[Try the current main build](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/dknauss/Borges/main/playground/blueprint-main.json)** — installs the development build of the `main` branch from the rolling `main-preview` pre-release, which CI refreshes on every push to `main`. Use this to preview unreleased changes ahead of the next release; it is not a stable build. +Every demo opens the block editor on a ready-made demo page, with the Welcome Guide out of the way: + +- Copy-ready samples of every accepted input: DOIs (bare, `doi:`, and doi.org links), PMIDs, PMCIDs, arXiv IDs (modern, legacy, arxiv.org links, and arXiv DOIs), ISBN-13 and ISBN-10, BibTeX and BibLaTeX entries, and formatted book, article, chapter, and webpage citations. An empty Bibliography block waits below them. +- Short notes on the key features: styles, editing, manual entry, duplicates, ordering, exports, per-entry Cite / Export, machine-readable output, and the REST data. +- Three example bibliographies: Chicago notes-bibliography with Cite / Export, APA 7, and IEEE. These need a release with the PHP port of `save()` (1.7.0 or later); older releases show a note in their place. + Both demo Blueprints explicitly request PHP `intl` support because editor-time CSL formatting runs through the plugin's local PHP formatter. The WordPress.org Preview blueprint is separate; WordPress.org installs Borges automatically there, and the blueprint only seeds demo content and auxiliary plugin setup. ## Screenshots @@ -357,7 +363,9 @@ The Playground demos and WordPress.org Preview all rely on the PHP formatter use - `.wordpress-org/blueprints/blueprint.json` deploys to WordPress.org SVN as `assets/blueprints/blueprint.json` for the plugin-directory Preview button. WordPress.org installs the plugin automatically in that preview, so this blueprint does not install Borges itself. - All three files intentionally declare `phpExtensionBundles: ["kitchen-sink"]` and `features: { "networking": true, "intl": true }`. The bundle form follows WordPress.org Preview documentation; the `features.intl` flag is required by the live browser Playground runtime so formatter requests do not fall back with `bibliography_builder_formatter_extension_missing`. -Run `npm run test -- --runTestsByPath src/blueprint.test.js` after editing any Blueprint. +The demo page is defined once, in `playground/demo-content.json` (samples, feature notes, example bibliographies) and `playground/demo-content.php` (the script that writes post 1 and turns off the Welcome Guide). Blueprints cannot load repository files, so `npm run playground:build` inlines both into each Blueprint's `runPHP` step; edit those two files, not the Blueprints' `runPHP` code. The example bibliographies are formatted by the plugin's own formatter and rendered by its PHP port of `save()` at boot, so they validate against whichever version the demo installs. + +Run `npm run test -- --runTestsByPath src/blueprint.test.js src/playground-demo.test.js` after editing any Blueprint or the demo content. The second file fails if a Blueprint is stale, if a sample stops reaching the parser backend it claims, or if an example's stored export strings drift from what the editor computes. ### Plugin File Structure diff --git a/docs/current-metrics.md b/docs/current-metrics.md index 3ecd54e..3d65414 100644 --- a/docs/current-metrics.md +++ b/docs/current-metrics.md @@ -12,7 +12,7 @@ Source and LOC figures last verified: **2026-09-25** against the locale-independ | Metric | Value | Re-derivation command | |---|---|---| | Main plugin file (`bibliography-builder.php`) | **1,978** | `wc -l bibliography-builder.php` | -| All first-party PHP (excl. vendor, tests, scripts, packages, output, node_modules, generated `build/`) | **6,428** | `find . -name '*.php' -not -path './vendor/*' -not -path './node_modules/*' -not -path './tests/*' -not -path './packages/*' -not -path './scripts/*' -not -path './output/*' -not -path './build/*' -print0 \| xargs -0 wc -l \| tail -1` | +| All first-party PHP (excl. vendor, tests, scripts, playground, packages, output, node_modules, generated `build/`) | **6,428** | `find . -name '*.php' -not -path './vendor/*' -not -path './node_modules/*' -not -path './tests/*' -not -path './packages/*' -not -path './scripts/*' -not -path './playground/*' -not -path './output/*' -not -path './build/*' -print0 \| xargs -0 wc -l \| tail -1` | | JS source (`src/`, excl. `*.test.js`) | **9,956** | `find ./src -name '*.js' -not -name '*.test.js' -print0 \| xargs -0 wc -l \| tail -1` | | Shipped frontend runtime (`build/view.js`, minified) | **1,449 bytes** | `npm run build` then `wc -c < build/view.js` | diff --git a/package.json b/package.json index e858b01..da28c67 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "assets:screenshots": "node scripts/capture-screenshots.js", "assets:icons": "node scripts/generate-icons.js", "assets:banners": "python3 scripts/generate_brand_assets.py", - "assets:banners:quick": "python3 scripts/generate_brand_assets.py --skip-render" + "assets:banners:quick": "python3 scripts/generate_brand_assets.py --skip-render", + "playground:build": "node ./scripts/build-playground-blueprints.js" }, "jest": { "testEnvironment": "jsdom", diff --git a/playground/blueprint-main.json b/playground/blueprint-main.json index d6cfecb..3946a6d 100644 --- a/playground/blueprint-main.json +++ b/playground/blueprint-main.json @@ -4,14 +4,20 @@ "title": "Borges Bibliography Builder (main build)", "description": "Launch WordPress Playground with the current development build of the Borges Bibliography Builder plugin from the main branch.", "author": "danknauss", - "categories": ["plugin", "bibliography", "block-editor"] + "categories": [ + "plugin", + "bibliography", + "block-editor" + ] }, "landingPage": "/wp-admin/post.php?post=1&action=edit", "preferredVersions": { "php": "8.3", "wp": "latest" }, - "phpExtensionBundles": ["kitchen-sink"], + "phpExtensionBundles": [ + "kitchen-sink" + ], "features": { "networking": true, "intl": true @@ -42,7 +48,7 @@ }, { "step": "runPHP", - "code": "\n

Welcome to the Borges Bibliography Builder development build. This Playground runs the current main branch, not a stable release. Use the Bibliography block below, then paste one or more sample entries into the Paste / Import field (separate entries with a blank line).

\n\n\n\n
10.1145/3368089.3409742\n\n10.1038/s41586-020-2649-2\n\nPMID:26673779\n\nPMC3531190\n\narXiv:1706.03762\n\nISBN 978-0-14-032872-1\n\n@article{knuth1984literate,\n  author = {Knuth, Donald E.},\n  title = {Literate Programming},\n  journal = {The Computer Journal},\n  year = {1984},\n  volume = {27},\n  number = {2},\n  pages = {97--111},\n  doi = {10.1093/comjnl/27.2.97}\n}
\n\n\n\n

Tip: In the block sidebar, try changing citation style and export actions (CSL-JSON, BibTeX, RIS).

\n\n\n\nEOT;\n\nwp_update_post(\n\tarray(\n\t\t'ID' => 1,\n\t\t'post_title' => 'Borges Bibliography Playground Starter (main build)',\n\t\t'post_content' => $post_content,\n\t)\n);\n" + "code": "Welcome to the Borges Bibliography Builder development build. This Playground runs the current main branch, not a stable release. It is a sandbox: nothing here is kept after you close the tab.';\n$borges_demo_json = <<<'JSON'\n{\"title\":\"Borges Bibliography Builder demo\",\"sampleGroups\":[{\"title\":\"DOIs\",\"description\":\"A bare DOI, a doi: prefix, or a doi.org link. Metadata comes from Crossref.\",\"samples\":[{\"format\":\"doi\",\"input\":\"10.1038/s41586-020-2649-2\"},{\"format\":\"doi\",\"input\":\"doi:10.1038/nature14539\"},{\"format\":\"doi\",\"input\":\"https://doi.org/10.1145/3368089.3409742\"}]},{\"title\":\"PubMed and PubMed Central\",\"description\":\"A PMID needs its PMID: label. A PMCID starts with PMC, with or without a PMCID: label.\",\"samples\":[{\"format\":\"pmid\",\"input\":\"PMID:26673779\"},{\"format\":\"pmcid\",\"input\":\"PMC3531190\"}]},{\"title\":\"arXiv\",\"description\":\"An arXiv: ID (modern or legacy), an arxiv.org link, or an arXiv DOI.\",\"samples\":[{\"format\":\"arxiv\",\"input\":\"arXiv:1706.03762\"},{\"format\":\"arxiv\",\"input\":\"https://arxiv.org/abs/1810.04805\"},{\"format\":\"arxiv\",\"input\":\"arXiv:hep-th/9711200\"},{\"format\":\"arxiv\",\"input\":\"10.48550/arXiv.2005.14165\"}]},{\"title\":\"ISBNs\",\"description\":\"A labeled ISBN-13 or ISBN-10, or a bare ISBN-13 starting with 978 or 979. Books come from Open Library, with Google Books as a fallback.\",\"samples\":[{\"format\":\"isbn\",\"input\":\"ISBN 978-0-14-032872-1\"},{\"format\":\"isbn\",\"input\":\"9780262033848\"},{\"format\":\"isbn\",\"input\":\"ISBN-10: 0-8112-0012-4\"}]},{\"title\":\"BibTeX and BibLaTeX\",\"description\":\"Whole entries, one or many. BibLaTeX fields such as date and location are understood.\",\"samples\":[{\"format\":\"bibtex\",\"input\":\"@article{knuth1984literate,\\n author = {Knuth, Donald E.},\\n title = {Literate Programming},\\n journal = {The Computer Journal},\\n year = {1984},\\n volume = {27},\\n number = {2},\\n pages = {97--111},\\n doi = {10.1093/comjnl/27.2.97}\\n}\"},{\"format\":\"bibtex\",\"input\":\"@book{borges1962labyrinths,\\n author = {Borges, Jorge Luis},\\n title = {Labyrinths: Selected Stories and Other Writings},\\n editor = {Yates, Donald A. and Irby, James E.},\\n date = {1962},\\n publisher = {New Directions},\\n location = {New York}\\n}\"}]},{\"title\":\"Formatted citations\",\"description\":\"Plain-text citations for books, journal articles, chapters, and webpages, in Chicago or APA shape. A citation that contains a DOI, PMID, PMCID, or arXiv ID is looked up by that identifier instead.\",\"samples\":[{\"format\":\"freetext\",\"input\":\"Binder, Amy J., and Jeffrey L. Kidder. The Channels of Student Activism: How the Left and Right Are Winning (and Losing) in Campus Politics Today. University of Chicago Press, 2022.\"},{\"format\":\"freetext\",\"input\":\"Dittmar, Emily L., and Douglas W. Schemske. “Temporal Variation in Selection Influences Microgeographic Local Adaptation.” American Naturalist 202, no. 4 (2023): 471–85.\"},{\"format\":\"freetext\",\"input\":\"Doyle, Kathleen. “The Queen Mary Psalter.” In The Book by Design: The Remarkable Story of the World’s Greatest Invention, edited by P. J. M. Marks and Stephen Parkin. University of Chicago Press, 2023.\"},{\"format\":\"freetext\",\"input\":\"Yale University. “About Yale: Yale Facts.” Accessed March 8, 2022. https://www.yale.edu/about-yale/yale-facts.\"},{\"format\":\"doi\",\"embedded\":true,\"input\":\"Dittmar, Emily L., and Douglas W. Schemske. “Temporal Variation in Selection Influences Microgeographic Local Adaptation.” American Naturalist 202, no. 4 (2023): 471–85. https://doi.org/10.1086/725865.\"}]}],\"features\":[\"Citation Style (block sidebar): switch among Chicago, APA, MLA, Harvard, IEEE, Vancouver, OSCOLA, ABNT, and more. Generated text reformats; your manual edits are kept.\",\"Edit an entry: select a citation and use its edit button to correct fields. Reset edits restores the generated text.\",\"Manual Entry: next to Paste / Import, add a citation field by field when there is nothing to paste.\",\"Duplicates: paste a DOI that is already in the list; it is skipped, not added twice.\",\"Order: notes and author-date styles sort alphabetically. Numeric styles (IEEE, Vancouver) keep your order; use Move citation up/down.\",\"Exports (block toolbar): copy the bibliography as text, or download BibTeX, BibLaTeX, RIS, or CSL-JSON.\",\"Per-entry Cite / Export (sidebar): gives readers copy and download links under each entry on the published page.\",\"Machine-readable output (sidebar): JSON-LD for search engines (on by default), COinS for Zotero, and embedded CSL-JSON.\",\"Front end: use View Post to see the published bibliography, then open its read-only REST data.\"],\"examples\":[{\"title\":\"Chicago notes-bibliography, with Cite / Export\",\"description\":\"A book, a chapter, a journal article, and a webpage, sorted alphabetically. Per-entry Cite / Export and COinS are on; view the post to try the copy and download links.\",\"attributes\":{\"bibliographyId\":\"demo-chicago-notes\",\"citationStyle\":\"chicago-notes-bibliography\",\"headingText\":\"Works Cited\",\"outputJsonLd\":true,\"outputCoins\":true,\"outputCslJson\":false,\"outputCiteExport\":true},\"citations\":[{\"type\":\"book\",\"title\":\"Labyrinths: Selected Stories and Other Writings\",\"author\":[{\"family\":\"Borges\",\"given\":\"Jorge Luis\"}],\"editor\":[{\"family\":\"Yates\",\"given\":\"Donald A.\"},{\"family\":\"Irby\",\"given\":\"James E.\"}],\"publisher\":\"New Directions\",\"publisher-place\":\"New York\",\"issued\":{\"date-parts\":[[1962]]}},{\"type\":\"chapter\",\"title\":\"The Library of Babel\",\"author\":[{\"family\":\"Borges\",\"given\":\"Jorge Luis\"}],\"translator\":[{\"family\":\"Irby\",\"given\":\"James E.\"}],\"container-title\":\"Labyrinths: Selected Stories and Other Writings\",\"editor\":[{\"family\":\"Yates\",\"given\":\"Donald A.\"},{\"family\":\"Irby\",\"given\":\"James E.\"}],\"publisher\":\"New Directions\",\"publisher-place\":\"New York\",\"issued\":{\"date-parts\":[[1962]]}},{\"type\":\"article-journal\",\"title\":\"Literate Programming\",\"author\":[{\"family\":\"Knuth\",\"given\":\"Donald E.\"}],\"container-title\":\"The Computer Journal\",\"volume\":\"27\",\"issue\":\"2\",\"page\":\"97-111\",\"DOI\":\"10.1093/comjnl/27.2.97\",\"issued\":{\"date-parts\":[[1984]]}},{\"type\":\"webpage\",\"title\":\"Block Editor Handbook\",\"author\":[{\"literal\":\"WordPress.org\"}],\"container-title\":\"WordPress Developer Resources\",\"URL\":\"https://developer.wordpress.org/block-editor/\"}],\"exports\":[{\"exportBibtex\":\"@book{Borges1962Labyrinths,\\n\\taddress = {New York},\\n\\tauthor = {Borges, Jorge Luis},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tyear = {1962},\\n\\tpublisher = {New Directions},\\n\\ttitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@book{Borges1962Labyrinths,\\n\\tauthor = {Borges, Jorge Luis},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tdate = {1962},\\n\\tlocation = {New York},\\n\\tpublisher = {New Directions},\\n\\ttitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n}\\n\\n\\n\"},{\"exportBibtex\":\"@inbook{Borges1962Library,\\n\\taddress = {New York},\\n\\tauthor = {Borges, Jorge Luis},\\n\\tbooktitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tyear = {1962},\\n\\tpublisher = {New Directions},\\n\\ttitle = {The {Library} of {Babel}},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@inbook{Borges1962Library,\\n\\tauthor = {Borges, Jorge Luis},\\n\\tbooktitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tdate = {1962},\\n\\tlocation = {New York},\\n\\tpublisher = {New Directions},\\n\\ttitle = {The {Library} of {Babel}},\\n\\ttranslator = {Irby, James E.},\\n}\\n\\n\\n\"},{\"exportBibtex\":\"@article{Knuth1984Literate,\\n\\tauthor = {Knuth, Donald E.},\\n\\tjournal = {The Computer Journal},\\n\\tdoi = {10.1093/comjnl/27.2.97},\\n\\tnumber = {2},\\n\\tyear = {1984},\\n\\tpages = {97--111},\\n\\ttitle = {Literate {Programming}},\\n\\tvolume = {27},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@article{Knuth1984Literate,\\n\\tauthor = {Knuth, Donald E.},\\n\\tjournaltitle = {The Computer Journal},\\n\\tdoi = {10.1093/comjnl/27.2.97},\\n\\tnumber = {2},\\n\\tdate = {1984},\\n\\tpages = {97--111},\\n\\ttitle = {Literate {Programming}},\\n\\tvolume = {27},\\n}\\n\\n\\n\"},{\"exportBibtex\":\"@misc{WordPressBlock,\\n\\tauthor = {{WordPress.org}},\\n\\ttitle = {Block {Editor} {Handbook}},\\n\\turl = {https://developer.wordpress.org/block-editor/},\\n\\thowpublished = {https://developer.wordpress.org/block-editor/},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@online{WordPressBlock,\\n\\tauthor = {{WordPress.org}},\\n\\tbooktitle = {WordPress {Developer} {Resources}},\\n\\ttitle = {Block {Editor} {Handbook}},\\n\\turl = {https://developer.wordpress.org/block-editor/},\\n}\\n\\n\\n\"}]},{\"title\":\"APA 7 author-date\",\"description\":\"Journal articles and an arXiv preprint in APA style, with DOIs as links.\",\"attributes\":{\"bibliographyId\":\"demo-apa-7\",\"citationStyle\":\"apa-7\",\"headingText\":\"References\",\"outputJsonLd\":true,\"outputCoins\":false,\"outputCslJson\":false,\"outputCiteExport\":false},\"citations\":[{\"type\":\"article-journal\",\"title\":\"Deep learning\",\"author\":[{\"family\":\"LeCun\",\"given\":\"Yann\"},{\"family\":\"Bengio\",\"given\":\"Yoshua\"},{\"family\":\"Hinton\",\"given\":\"Geoffrey\"}],\"container-title\":\"Nature\",\"volume\":\"521\",\"issue\":\"7553\",\"page\":\"436-444\",\"DOI\":\"10.1038/nature14539\",\"issued\":{\"date-parts\":[[2015]]}},{\"type\":\"article\",\"title\":\"Attention is all you need\",\"author\":[{\"family\":\"Vaswani\",\"given\":\"Ashish\"},{\"family\":\"Shazeer\",\"given\":\"Noam\"},{\"family\":\"Parmar\",\"given\":\"Niki\"},{\"family\":\"Uszkoreit\",\"given\":\"Jakob\"},{\"family\":\"Jones\",\"given\":\"Llion\"},{\"family\":\"Gomez\",\"given\":\"Aidan N.\"},{\"family\":\"Kaiser\",\"given\":\"Łukasz\"},{\"family\":\"Polosukhin\",\"given\":\"Illia\"}],\"publisher\":\"arXiv\",\"number\":\"1706.03762\",\"DOI\":\"10.48550/arXiv.1706.03762\",\"issued\":{\"date-parts\":[[2017]]}},{\"type\":\"article-journal\",\"title\":\"Computing machinery and intelligence\",\"author\":[{\"family\":\"Turing\",\"given\":\"A. M.\"}],\"container-title\":\"Mind\",\"volume\":\"59\",\"issue\":\"236\",\"page\":\"433-460\",\"DOI\":\"10.1093/mind/LIX.236.433\",\"issued\":{\"date-parts\":[[1950]]}}]},{\"title\":\"IEEE numeric\",\"description\":\"Numbered entries keep the order you give them. Select the block and use Move citation up/down to renumber.\",\"attributes\":{\"bibliographyId\":\"demo-ieee\",\"citationStyle\":\"ieee\",\"headingText\":\"References\",\"outputJsonLd\":true,\"outputCoins\":false,\"outputCslJson\":false,\"outputCiteExport\":false},\"citations\":[{\"type\":\"article-journal\",\"title\":\"A Mathematical Theory of Communication\",\"author\":[{\"family\":\"Shannon\",\"given\":\"Claude E.\"}],\"container-title\":\"Bell System Technical Journal\",\"volume\":\"27\",\"issue\":\"3\",\"page\":\"379-423\",\"DOI\":\"10.1002/j.1538-7305.1948.tb01338.x\",\"issued\":{\"date-parts\":[[1948]]}},{\"type\":\"article-journal\",\"title\":\"Computing Machinery and Intelligence\",\"author\":[{\"family\":\"Turing\",\"given\":\"A. M.\"}],\"container-title\":\"Mind\",\"volume\":\"59\",\"issue\":\"236\",\"page\":\"433-460\",\"DOI\":\"10.1093/mind/LIX.236.433\",\"issued\":{\"date-parts\":[[1950]]}},{\"type\":\"article-journal\",\"title\":\"Literate Programming\",\"author\":[{\"family\":\"Knuth\",\"given\":\"Donald E.\"}],\"container-title\":\"The Computer Journal\",\"volume\":\"27\",\"issue\":\"2\",\"page\":\"97-111\",\"DOI\":\"10.1093/comjnl/27.2.97\",\"issued\":{\"date-parts\":[[1984]]}}]}]}\nJSON;\n/**\n * Playground demo page builder.\n *\n * `npm run playground:build` inlines this file into each demo blueprint's\n * runPHP step, after defining:\n *\n * - $borges_demo_json The contents of playground/demo-content.json.\n * - $borges_demo_intro An HTML lead paragraph for that blueprint.\n *\n * It rewrites post 1 as the demo page the blueprint lands on, and turns off\n * the editor's Welcome Guide so the page is visible straight away. Example\n * bibliographies are formatted with the plugin's own citeproc formatter and\n * rendered with its PHP port of save(), so they validate against whatever\n * version is installed. A release without that port (before 1.7.0) gets a\n * note instead.\n *\n * Not part of the plugin package (playground/ is in .distignore).\n *\n * @package BibliographyBuilder\n */\n\nrequire_once '/wordpress/wp-load.php';\n\n// Write as the admin, and keep kses from stripping the blocks' JSON-LD\n// scripts and data attributes: kses_init() ran before any user was set.\nwp_set_current_user( 1 );\nkses_remove_filters();\n\n$borges_demo = json_decode( $borges_demo_json, true );\n\n$borges_demo_block = static function ( $name, $attrs, $html ) {\n\treturn serialize_block(\n\t\tarray(\n\t\t\t'blockName' => $name,\n\t\t\t'attrs' => $attrs,\n\t\t\t'innerBlocks' => array(),\n\t\t\t'innerHTML' => $html,\n\t\t\t'innerContent' => array( $html ),\n\t\t)\n\t) . \"\\n\\n\";\n};\n\n$borges_demo_paragraph = static function ( $html ) use ( $borges_demo_block ) {\n\treturn $borges_demo_block( 'core/paragraph', array(), '

' . $html . '

' );\n};\n\n$borges_demo_heading = static function ( $text, $level ) use ( $borges_demo_block ) {\n\t$attrs = 2 === $level ? array() : array( 'level' => $level );\n\t$tag = 'h' . $level;\n\n\treturn $borges_demo_block( 'core/heading', $attrs, '<' . $tag . ' class=\"wp-block-heading\">' . esc_html( $text ) . '' );\n};\n\n$borges_demo_code = static function ( $text ) use ( $borges_demo_block ) {\n\treturn $borges_demo_block(\n\t\t'core/code',\n\t\tarray(),\n\t\t'
' . htmlspecialchars( $text, ENT_NOQUOTES, 'UTF-8' ) . '
'\n\t);\n};\n\n$borges_demo_list = static function ( $items ) {\n\t$html = \"\\n\\n\\n\\n\";\n};\n\n$borges_demo_can_prebuild = function_exists( 'bibliography_builder_render_save_markup' )\n\t&& function_exists( 'bibliography_builder_format_csl_items' )\n\t&& bibliography_builder_can_render_save_markup();\n\n// Returns the serialized block, or '' when this install cannot build it.\n$borges_demo_bibliography = static function ( $example ) use ( $borges_demo_can_prebuild ) {\n\tif ( ! $borges_demo_can_prebuild ) {\n\t\treturn '';\n\t}\n\n\t$csl_items = bibliography_builder_validate_and_sanitize_csl_items( $example['citations'] );\n\n\tif ( is_wp_error( $csl_items ) ) {\n\t\treturn '';\n\t}\n\n\t$formatted = bibliography_builder_format_csl_items( $csl_items, $example['attributes']['citationStyle'] );\n\n\tif ( is_wp_error( $formatted ) ) {\n\t\treturn '';\n\t}\n\n\t$citations = array();\n\n\tforeach ( array_values( $csl_items ) as $index => $csl ) {\n\t\t$citation = array(\n\t\t\t'id' => $example['attributes']['bibliographyId'] . '-' . ( $index + 1 ),\n\t\t\t'csl' => $csl,\n\t\t\t'formattedText' => $formatted[ $index ],\n\t\t);\n\n\t\t// The editor stores these for Cite / Export; without them it adds\n\t\t// them on load and the untouched demo opens with unsaved changes.\n\t\tforeach ( array( 'exportBibtex', 'exportBiblatex' ) as $export_key ) {\n\t\t\tif ( isset( $example['exports'][ $index ][ $export_key ] ) ) {\n\t\t\t\t$citation[ $export_key ] = $example['exports'][ $index ][ $export_key ];\n\t\t\t}\n\t\t}\n\n\t\t$citations[] = $citation;\n\t}\n\n\t$attrs = $example['attributes'];\n\t// Stored in display order, as the editor stores them.\n\t$attrs['citations'] = bibliography_builder_sort_citations_for_save( $citations, $attrs['citationStyle'] );\n\t// Render from the attributes as the editor will parse them back.\n\t$html = bibliography_builder_render_save_markup(\n\t\tbibliography_builder_decode_save_attributes( wp_json_encode( $attrs ) )\n\t);\n\n\tif ( '' === $html ) {\n\t\treturn '';\n\t}\n\n\treturn serialize_block(\n\t\tarray(\n\t\t\t'blockName' => 'bibliography-builder/bibliography',\n\t\t\t'attrs' => $attrs,\n\t\t\t'innerBlocks' => array(),\n\t\t\t'innerHTML' => $html,\n\t\t\t'innerContent' => array( $html ),\n\t\t)\n\t) . \"\\n\\n\";\n};\n\n$content = $borges_demo_paragraph( $borges_demo_intro );\n\n$content .= $borges_demo_heading( '1. Paste any supported format', 2 );\n$content .= $borges_demo_paragraph(\n\t'Select the empty Bibliography block at the end of this section, open Paste / Import, paste one or more samples, and choose Add. Separate entries with a blank line; up to 50 go in one paste, so you can copy several groups at once.'\n);\n\nforeach ( $borges_demo['sampleGroups'] as $group ) {\n\t$content .= $borges_demo_heading( $group['title'], 3 );\n\t$content .= $borges_demo_paragraph( $group['description'] );\n\t$content .= $borges_demo_code( implode( \"\\n\\n\", wp_list_pluck( $group['samples'], 'input' ) ) );\n}\n\n$content .= $borges_demo_block(\n\t'bibliography-builder/bibliography',\n\tarray(\n\t\t'bibliographyId' => 'demo-try-it',\n\t\t'headingText' => 'References',\n\t),\n\t''\n);\n\n$content .= $borges_demo_heading( '2. Key features to try', 2 );\n$content .= $borges_demo_list( $borges_demo['features'] );\n\n$content .= $borges_demo_heading( '3. Example bibliographies', 2 );\n\n$borges_demo_examples = '';\n\nforeach ( $borges_demo['examples'] as $example ) {\n\t$block = $borges_demo_bibliography( $example );\n\n\tif ( '' === $block ) {\n\t\tcontinue;\n\t}\n\n\t$borges_demo_examples .= $borges_demo_heading( $example['title'], 3 );\n\t$borges_demo_examples .= $borges_demo_paragraph( $example['description'] );\n\t$borges_demo_examples .= $block;\n}\n\n$content .= '' !== $borges_demo_examples\n\t? $borges_demo_paragraph( 'Each block below was built from CSL-JSON. Select one to change its style or settings.' ) . $borges_demo_examples\n\t: $borges_demo_paragraph( 'Pre-built examples need Borges 1.7.0 or later. Paste a few samples from section 1 to build your own.' );\n\nwp_update_post(\n\tarray(\n\t\t'ID' => 1,\n\t\t'post_title' => $borges_demo['title'],\n\t\t'post_content' => wp_slash( $content ),\n\t)\n);\n\n// Skip the block editor's Welcome Guide so the demo page is what opens.\n$borges_demo_meta_key = $GLOBALS['wpdb']->get_blog_prefix() . 'persisted_preferences';\n$borges_demo_preferences = get_user_meta( 1, $borges_demo_meta_key, true );\n$borges_demo_preferences = is_array( $borges_demo_preferences ) ? $borges_demo_preferences : array();\n\n$borges_demo_preferences['core/edit-post'] = array_merge(\n\tisset( $borges_demo_preferences['core/edit-post'] ) && is_array( $borges_demo_preferences['core/edit-post'] )\n\t\t? $borges_demo_preferences['core/edit-post']\n\t\t: array(),\n\tarray( 'welcomeGuide' => false )\n);\n$borges_demo_preferences['_modified'] = gmdate( 'c' );\n\nupdate_user_meta( 1, $borges_demo_meta_key, $borges_demo_preferences );\n" } ] } diff --git a/playground/blueprint.json b/playground/blueprint.json index 6b7e783..7458610 100644 --- a/playground/blueprint.json +++ b/playground/blueprint.json @@ -4,14 +4,20 @@ "title": "Borges Bibliography Builder", "description": "Launch WordPress Playground with the Borges Bibliography Builder plugin installed for quick evaluation.", "author": "danknauss", - "categories": ["plugin", "bibliography", "block-editor"] + "categories": [ + "plugin", + "bibliography", + "block-editor" + ] }, "landingPage": "/wp-admin/post.php?post=1&action=edit", "preferredVersions": { "php": "8.3", "wp": "latest" }, - "phpExtensionBundles": ["kitchen-sink"], + "phpExtensionBundles": [ + "kitchen-sink" + ], "features": { "networking": true, "intl": true @@ -42,7 +48,7 @@ }, { "step": "runPHP", - "code": "\n

Welcome to Borges Bibliography Builder. Use the Bibliography block below, then paste one or more sample entries into the Paste / Import field (separate entries with a blank line).

\n\n\n\n
10.1145/3368089.3409742\n\n10.1038/s41586-020-2649-2\n\nPMID:26673779\n\nPMC3531190\n\narXiv:1706.03762\n\nISBN 978-0-14-032872-1\n\n@article{knuth1984literate,\n  author = {Knuth, Donald E.},\n  title = {Literate Programming},\n  journal = {The Computer Journal},\n  year = {1984},\n  volume = {27},\n  number = {2},\n  pages = {97--111},\n  doi = {10.1093/comjnl/27.2.97}\n}
\n\n\n\n

Tip: In the block sidebar, try changing citation style and export actions (CSL-JSON, BibTeX, RIS).

\n\n\n\nEOT;\n\nwp_update_post(\n\tarray(\n\t\t'ID' => 1,\n\t\t'post_title' => 'Borges Bibliography Playground Starter',\n\t\t'post_content' => $post_content,\n\t)\n);\n" + "code": "Welcome to Borges Bibliography Builder. This Playground runs the latest release. It is a sandbox: nothing here is kept after you close the tab.';\n$borges_demo_json = <<<'JSON'\n{\"title\":\"Borges Bibliography Builder demo\",\"sampleGroups\":[{\"title\":\"DOIs\",\"description\":\"A bare DOI, a doi: prefix, or a doi.org link. Metadata comes from Crossref.\",\"samples\":[{\"format\":\"doi\",\"input\":\"10.1038/s41586-020-2649-2\"},{\"format\":\"doi\",\"input\":\"doi:10.1038/nature14539\"},{\"format\":\"doi\",\"input\":\"https://doi.org/10.1145/3368089.3409742\"}]},{\"title\":\"PubMed and PubMed Central\",\"description\":\"A PMID needs its PMID: label. A PMCID starts with PMC, with or without a PMCID: label.\",\"samples\":[{\"format\":\"pmid\",\"input\":\"PMID:26673779\"},{\"format\":\"pmcid\",\"input\":\"PMC3531190\"}]},{\"title\":\"arXiv\",\"description\":\"An arXiv: ID (modern or legacy), an arxiv.org link, or an arXiv DOI.\",\"samples\":[{\"format\":\"arxiv\",\"input\":\"arXiv:1706.03762\"},{\"format\":\"arxiv\",\"input\":\"https://arxiv.org/abs/1810.04805\"},{\"format\":\"arxiv\",\"input\":\"arXiv:hep-th/9711200\"},{\"format\":\"arxiv\",\"input\":\"10.48550/arXiv.2005.14165\"}]},{\"title\":\"ISBNs\",\"description\":\"A labeled ISBN-13 or ISBN-10, or a bare ISBN-13 starting with 978 or 979. Books come from Open Library, with Google Books as a fallback.\",\"samples\":[{\"format\":\"isbn\",\"input\":\"ISBN 978-0-14-032872-1\"},{\"format\":\"isbn\",\"input\":\"9780262033848\"},{\"format\":\"isbn\",\"input\":\"ISBN-10: 0-8112-0012-4\"}]},{\"title\":\"BibTeX and BibLaTeX\",\"description\":\"Whole entries, one or many. BibLaTeX fields such as date and location are understood.\",\"samples\":[{\"format\":\"bibtex\",\"input\":\"@article{knuth1984literate,\\n author = {Knuth, Donald E.},\\n title = {Literate Programming},\\n journal = {The Computer Journal},\\n year = {1984},\\n volume = {27},\\n number = {2},\\n pages = {97--111},\\n doi = {10.1093/comjnl/27.2.97}\\n}\"},{\"format\":\"bibtex\",\"input\":\"@book{borges1962labyrinths,\\n author = {Borges, Jorge Luis},\\n title = {Labyrinths: Selected Stories and Other Writings},\\n editor = {Yates, Donald A. and Irby, James E.},\\n date = {1962},\\n publisher = {New Directions},\\n location = {New York}\\n}\"}]},{\"title\":\"Formatted citations\",\"description\":\"Plain-text citations for books, journal articles, chapters, and webpages, in Chicago or APA shape. A citation that contains a DOI, PMID, PMCID, or arXiv ID is looked up by that identifier instead.\",\"samples\":[{\"format\":\"freetext\",\"input\":\"Binder, Amy J., and Jeffrey L. Kidder. The Channels of Student Activism: How the Left and Right Are Winning (and Losing) in Campus Politics Today. University of Chicago Press, 2022.\"},{\"format\":\"freetext\",\"input\":\"Dittmar, Emily L., and Douglas W. Schemske. “Temporal Variation in Selection Influences Microgeographic Local Adaptation.” American Naturalist 202, no. 4 (2023): 471–85.\"},{\"format\":\"freetext\",\"input\":\"Doyle, Kathleen. “The Queen Mary Psalter.” In The Book by Design: The Remarkable Story of the World’s Greatest Invention, edited by P. J. M. Marks and Stephen Parkin. University of Chicago Press, 2023.\"},{\"format\":\"freetext\",\"input\":\"Yale University. “About Yale: Yale Facts.” Accessed March 8, 2022. https://www.yale.edu/about-yale/yale-facts.\"},{\"format\":\"doi\",\"embedded\":true,\"input\":\"Dittmar, Emily L., and Douglas W. Schemske. “Temporal Variation in Selection Influences Microgeographic Local Adaptation.” American Naturalist 202, no. 4 (2023): 471–85. https://doi.org/10.1086/725865.\"}]}],\"features\":[\"Citation Style (block sidebar): switch among Chicago, APA, MLA, Harvard, IEEE, Vancouver, OSCOLA, ABNT, and more. Generated text reformats; your manual edits are kept.\",\"Edit an entry: select a citation and use its edit button to correct fields. Reset edits restores the generated text.\",\"Manual Entry: next to Paste / Import, add a citation field by field when there is nothing to paste.\",\"Duplicates: paste a DOI that is already in the list; it is skipped, not added twice.\",\"Order: notes and author-date styles sort alphabetically. Numeric styles (IEEE, Vancouver) keep your order; use Move citation up/down.\",\"Exports (block toolbar): copy the bibliography as text, or download BibTeX, BibLaTeX, RIS, or CSL-JSON.\",\"Per-entry Cite / Export (sidebar): gives readers copy and download links under each entry on the published page.\",\"Machine-readable output (sidebar): JSON-LD for search engines (on by default), COinS for Zotero, and embedded CSL-JSON.\",\"Front end: use View Post to see the published bibliography, then open its read-only REST data.\"],\"examples\":[{\"title\":\"Chicago notes-bibliography, with Cite / Export\",\"description\":\"A book, a chapter, a journal article, and a webpage, sorted alphabetically. Per-entry Cite / Export and COinS are on; view the post to try the copy and download links.\",\"attributes\":{\"bibliographyId\":\"demo-chicago-notes\",\"citationStyle\":\"chicago-notes-bibliography\",\"headingText\":\"Works Cited\",\"outputJsonLd\":true,\"outputCoins\":true,\"outputCslJson\":false,\"outputCiteExport\":true},\"citations\":[{\"type\":\"book\",\"title\":\"Labyrinths: Selected Stories and Other Writings\",\"author\":[{\"family\":\"Borges\",\"given\":\"Jorge Luis\"}],\"editor\":[{\"family\":\"Yates\",\"given\":\"Donald A.\"},{\"family\":\"Irby\",\"given\":\"James E.\"}],\"publisher\":\"New Directions\",\"publisher-place\":\"New York\",\"issued\":{\"date-parts\":[[1962]]}},{\"type\":\"chapter\",\"title\":\"The Library of Babel\",\"author\":[{\"family\":\"Borges\",\"given\":\"Jorge Luis\"}],\"translator\":[{\"family\":\"Irby\",\"given\":\"James E.\"}],\"container-title\":\"Labyrinths: Selected Stories and Other Writings\",\"editor\":[{\"family\":\"Yates\",\"given\":\"Donald A.\"},{\"family\":\"Irby\",\"given\":\"James E.\"}],\"publisher\":\"New Directions\",\"publisher-place\":\"New York\",\"issued\":{\"date-parts\":[[1962]]}},{\"type\":\"article-journal\",\"title\":\"Literate Programming\",\"author\":[{\"family\":\"Knuth\",\"given\":\"Donald E.\"}],\"container-title\":\"The Computer Journal\",\"volume\":\"27\",\"issue\":\"2\",\"page\":\"97-111\",\"DOI\":\"10.1093/comjnl/27.2.97\",\"issued\":{\"date-parts\":[[1984]]}},{\"type\":\"webpage\",\"title\":\"Block Editor Handbook\",\"author\":[{\"literal\":\"WordPress.org\"}],\"container-title\":\"WordPress Developer Resources\",\"URL\":\"https://developer.wordpress.org/block-editor/\"}],\"exports\":[{\"exportBibtex\":\"@book{Borges1962Labyrinths,\\n\\taddress = {New York},\\n\\tauthor = {Borges, Jorge Luis},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tyear = {1962},\\n\\tpublisher = {New Directions},\\n\\ttitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@book{Borges1962Labyrinths,\\n\\tauthor = {Borges, Jorge Luis},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tdate = {1962},\\n\\tlocation = {New York},\\n\\tpublisher = {New Directions},\\n\\ttitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n}\\n\\n\\n\"},{\"exportBibtex\":\"@inbook{Borges1962Library,\\n\\taddress = {New York},\\n\\tauthor = {Borges, Jorge Luis},\\n\\tbooktitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tyear = {1962},\\n\\tpublisher = {New Directions},\\n\\ttitle = {The {Library} of {Babel}},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@inbook{Borges1962Library,\\n\\tauthor = {Borges, Jorge Luis},\\n\\tbooktitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\\n\\teditor = {Yates, Donald A. and Irby, James E.},\\n\\tdate = {1962},\\n\\tlocation = {New York},\\n\\tpublisher = {New Directions},\\n\\ttitle = {The {Library} of {Babel}},\\n\\ttranslator = {Irby, James E.},\\n}\\n\\n\\n\"},{\"exportBibtex\":\"@article{Knuth1984Literate,\\n\\tauthor = {Knuth, Donald E.},\\n\\tjournal = {The Computer Journal},\\n\\tdoi = {10.1093/comjnl/27.2.97},\\n\\tnumber = {2},\\n\\tyear = {1984},\\n\\tpages = {97--111},\\n\\ttitle = {Literate {Programming}},\\n\\tvolume = {27},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@article{Knuth1984Literate,\\n\\tauthor = {Knuth, Donald E.},\\n\\tjournaltitle = {The Computer Journal},\\n\\tdoi = {10.1093/comjnl/27.2.97},\\n\\tnumber = {2},\\n\\tdate = {1984},\\n\\tpages = {97--111},\\n\\ttitle = {Literate {Programming}},\\n\\tvolume = {27},\\n}\\n\\n\\n\"},{\"exportBibtex\":\"@misc{WordPressBlock,\\n\\tauthor = {{WordPress.org}},\\n\\ttitle = {Block {Editor} {Handbook}},\\n\\turl = {https://developer.wordpress.org/block-editor/},\\n\\thowpublished = {https://developer.wordpress.org/block-editor/},\\n}\\n\\n\\n\",\"exportBiblatex\":\"@online{WordPressBlock,\\n\\tauthor = {{WordPress.org}},\\n\\tbooktitle = {WordPress {Developer} {Resources}},\\n\\ttitle = {Block {Editor} {Handbook}},\\n\\turl = {https://developer.wordpress.org/block-editor/},\\n}\\n\\n\\n\"}]},{\"title\":\"APA 7 author-date\",\"description\":\"Journal articles and an arXiv preprint in APA style, with DOIs as links.\",\"attributes\":{\"bibliographyId\":\"demo-apa-7\",\"citationStyle\":\"apa-7\",\"headingText\":\"References\",\"outputJsonLd\":true,\"outputCoins\":false,\"outputCslJson\":false,\"outputCiteExport\":false},\"citations\":[{\"type\":\"article-journal\",\"title\":\"Deep learning\",\"author\":[{\"family\":\"LeCun\",\"given\":\"Yann\"},{\"family\":\"Bengio\",\"given\":\"Yoshua\"},{\"family\":\"Hinton\",\"given\":\"Geoffrey\"}],\"container-title\":\"Nature\",\"volume\":\"521\",\"issue\":\"7553\",\"page\":\"436-444\",\"DOI\":\"10.1038/nature14539\",\"issued\":{\"date-parts\":[[2015]]}},{\"type\":\"article\",\"title\":\"Attention is all you need\",\"author\":[{\"family\":\"Vaswani\",\"given\":\"Ashish\"},{\"family\":\"Shazeer\",\"given\":\"Noam\"},{\"family\":\"Parmar\",\"given\":\"Niki\"},{\"family\":\"Uszkoreit\",\"given\":\"Jakob\"},{\"family\":\"Jones\",\"given\":\"Llion\"},{\"family\":\"Gomez\",\"given\":\"Aidan N.\"},{\"family\":\"Kaiser\",\"given\":\"Łukasz\"},{\"family\":\"Polosukhin\",\"given\":\"Illia\"}],\"publisher\":\"arXiv\",\"number\":\"1706.03762\",\"DOI\":\"10.48550/arXiv.1706.03762\",\"issued\":{\"date-parts\":[[2017]]}},{\"type\":\"article-journal\",\"title\":\"Computing machinery and intelligence\",\"author\":[{\"family\":\"Turing\",\"given\":\"A. M.\"}],\"container-title\":\"Mind\",\"volume\":\"59\",\"issue\":\"236\",\"page\":\"433-460\",\"DOI\":\"10.1093/mind/LIX.236.433\",\"issued\":{\"date-parts\":[[1950]]}}]},{\"title\":\"IEEE numeric\",\"description\":\"Numbered entries keep the order you give them. Select the block and use Move citation up/down to renumber.\",\"attributes\":{\"bibliographyId\":\"demo-ieee\",\"citationStyle\":\"ieee\",\"headingText\":\"References\",\"outputJsonLd\":true,\"outputCoins\":false,\"outputCslJson\":false,\"outputCiteExport\":false},\"citations\":[{\"type\":\"article-journal\",\"title\":\"A Mathematical Theory of Communication\",\"author\":[{\"family\":\"Shannon\",\"given\":\"Claude E.\"}],\"container-title\":\"Bell System Technical Journal\",\"volume\":\"27\",\"issue\":\"3\",\"page\":\"379-423\",\"DOI\":\"10.1002/j.1538-7305.1948.tb01338.x\",\"issued\":{\"date-parts\":[[1948]]}},{\"type\":\"article-journal\",\"title\":\"Computing Machinery and Intelligence\",\"author\":[{\"family\":\"Turing\",\"given\":\"A. M.\"}],\"container-title\":\"Mind\",\"volume\":\"59\",\"issue\":\"236\",\"page\":\"433-460\",\"DOI\":\"10.1093/mind/LIX.236.433\",\"issued\":{\"date-parts\":[[1950]]}},{\"type\":\"article-journal\",\"title\":\"Literate Programming\",\"author\":[{\"family\":\"Knuth\",\"given\":\"Donald E.\"}],\"container-title\":\"The Computer Journal\",\"volume\":\"27\",\"issue\":\"2\",\"page\":\"97-111\",\"DOI\":\"10.1093/comjnl/27.2.97\",\"issued\":{\"date-parts\":[[1984]]}}]}]}\nJSON;\n/**\n * Playground demo page builder.\n *\n * `npm run playground:build` inlines this file into each demo blueprint's\n * runPHP step, after defining:\n *\n * - $borges_demo_json The contents of playground/demo-content.json.\n * - $borges_demo_intro An HTML lead paragraph for that blueprint.\n *\n * It rewrites post 1 as the demo page the blueprint lands on, and turns off\n * the editor's Welcome Guide so the page is visible straight away. Example\n * bibliographies are formatted with the plugin's own citeproc formatter and\n * rendered with its PHP port of save(), so they validate against whatever\n * version is installed. A release without that port (before 1.7.0) gets a\n * note instead.\n *\n * Not part of the plugin package (playground/ is in .distignore).\n *\n * @package BibliographyBuilder\n */\n\nrequire_once '/wordpress/wp-load.php';\n\n// Write as the admin, and keep kses from stripping the blocks' JSON-LD\n// scripts and data attributes: kses_init() ran before any user was set.\nwp_set_current_user( 1 );\nkses_remove_filters();\n\n$borges_demo = json_decode( $borges_demo_json, true );\n\n$borges_demo_block = static function ( $name, $attrs, $html ) {\n\treturn serialize_block(\n\t\tarray(\n\t\t\t'blockName' => $name,\n\t\t\t'attrs' => $attrs,\n\t\t\t'innerBlocks' => array(),\n\t\t\t'innerHTML' => $html,\n\t\t\t'innerContent' => array( $html ),\n\t\t)\n\t) . \"\\n\\n\";\n};\n\n$borges_demo_paragraph = static function ( $html ) use ( $borges_demo_block ) {\n\treturn $borges_demo_block( 'core/paragraph', array(), '

' . $html . '

' );\n};\n\n$borges_demo_heading = static function ( $text, $level ) use ( $borges_demo_block ) {\n\t$attrs = 2 === $level ? array() : array( 'level' => $level );\n\t$tag = 'h' . $level;\n\n\treturn $borges_demo_block( 'core/heading', $attrs, '<' . $tag . ' class=\"wp-block-heading\">' . esc_html( $text ) . '' );\n};\n\n$borges_demo_code = static function ( $text ) use ( $borges_demo_block ) {\n\treturn $borges_demo_block(\n\t\t'core/code',\n\t\tarray(),\n\t\t'
' . htmlspecialchars( $text, ENT_NOQUOTES, 'UTF-8' ) . '
'\n\t);\n};\n\n$borges_demo_list = static function ( $items ) {\n\t$html = \"\\n\\n\\n\\n\";\n};\n\n$borges_demo_can_prebuild = function_exists( 'bibliography_builder_render_save_markup' )\n\t&& function_exists( 'bibliography_builder_format_csl_items' )\n\t&& bibliography_builder_can_render_save_markup();\n\n// Returns the serialized block, or '' when this install cannot build it.\n$borges_demo_bibliography = static function ( $example ) use ( $borges_demo_can_prebuild ) {\n\tif ( ! $borges_demo_can_prebuild ) {\n\t\treturn '';\n\t}\n\n\t$csl_items = bibliography_builder_validate_and_sanitize_csl_items( $example['citations'] );\n\n\tif ( is_wp_error( $csl_items ) ) {\n\t\treturn '';\n\t}\n\n\t$formatted = bibliography_builder_format_csl_items( $csl_items, $example['attributes']['citationStyle'] );\n\n\tif ( is_wp_error( $formatted ) ) {\n\t\treturn '';\n\t}\n\n\t$citations = array();\n\n\tforeach ( array_values( $csl_items ) as $index => $csl ) {\n\t\t$citation = array(\n\t\t\t'id' => $example['attributes']['bibliographyId'] . '-' . ( $index + 1 ),\n\t\t\t'csl' => $csl,\n\t\t\t'formattedText' => $formatted[ $index ],\n\t\t);\n\n\t\t// The editor stores these for Cite / Export; without them it adds\n\t\t// them on load and the untouched demo opens with unsaved changes.\n\t\tforeach ( array( 'exportBibtex', 'exportBiblatex' ) as $export_key ) {\n\t\t\tif ( isset( $example['exports'][ $index ][ $export_key ] ) ) {\n\t\t\t\t$citation[ $export_key ] = $example['exports'][ $index ][ $export_key ];\n\t\t\t}\n\t\t}\n\n\t\t$citations[] = $citation;\n\t}\n\n\t$attrs = $example['attributes'];\n\t// Stored in display order, as the editor stores them.\n\t$attrs['citations'] = bibliography_builder_sort_citations_for_save( $citations, $attrs['citationStyle'] );\n\t// Render from the attributes as the editor will parse them back.\n\t$html = bibliography_builder_render_save_markup(\n\t\tbibliography_builder_decode_save_attributes( wp_json_encode( $attrs ) )\n\t);\n\n\tif ( '' === $html ) {\n\t\treturn '';\n\t}\n\n\treturn serialize_block(\n\t\tarray(\n\t\t\t'blockName' => 'bibliography-builder/bibliography',\n\t\t\t'attrs' => $attrs,\n\t\t\t'innerBlocks' => array(),\n\t\t\t'innerHTML' => $html,\n\t\t\t'innerContent' => array( $html ),\n\t\t)\n\t) . \"\\n\\n\";\n};\n\n$content = $borges_demo_paragraph( $borges_demo_intro );\n\n$content .= $borges_demo_heading( '1. Paste any supported format', 2 );\n$content .= $borges_demo_paragraph(\n\t'Select the empty Bibliography block at the end of this section, open Paste / Import, paste one or more samples, and choose Add. Separate entries with a blank line; up to 50 go in one paste, so you can copy several groups at once.'\n);\n\nforeach ( $borges_demo['sampleGroups'] as $group ) {\n\t$content .= $borges_demo_heading( $group['title'], 3 );\n\t$content .= $borges_demo_paragraph( $group['description'] );\n\t$content .= $borges_demo_code( implode( \"\\n\\n\", wp_list_pluck( $group['samples'], 'input' ) ) );\n}\n\n$content .= $borges_demo_block(\n\t'bibliography-builder/bibliography',\n\tarray(\n\t\t'bibliographyId' => 'demo-try-it',\n\t\t'headingText' => 'References',\n\t),\n\t''\n);\n\n$content .= $borges_demo_heading( '2. Key features to try', 2 );\n$content .= $borges_demo_list( $borges_demo['features'] );\n\n$content .= $borges_demo_heading( '3. Example bibliographies', 2 );\n\n$borges_demo_examples = '';\n\nforeach ( $borges_demo['examples'] as $example ) {\n\t$block = $borges_demo_bibliography( $example );\n\n\tif ( '' === $block ) {\n\t\tcontinue;\n\t}\n\n\t$borges_demo_examples .= $borges_demo_heading( $example['title'], 3 );\n\t$borges_demo_examples .= $borges_demo_paragraph( $example['description'] );\n\t$borges_demo_examples .= $block;\n}\n\n$content .= '' !== $borges_demo_examples\n\t? $borges_demo_paragraph( 'Each block below was built from CSL-JSON. Select one to change its style or settings.' ) . $borges_demo_examples\n\t: $borges_demo_paragraph( 'Pre-built examples need Borges 1.7.0 or later. Paste a few samples from section 1 to build your own.' );\n\nwp_update_post(\n\tarray(\n\t\t'ID' => 1,\n\t\t'post_title' => $borges_demo['title'],\n\t\t'post_content' => wp_slash( $content ),\n\t)\n);\n\n// Skip the block editor's Welcome Guide so the demo page is what opens.\n$borges_demo_meta_key = $GLOBALS['wpdb']->get_blog_prefix() . 'persisted_preferences';\n$borges_demo_preferences = get_user_meta( 1, $borges_demo_meta_key, true );\n$borges_demo_preferences = is_array( $borges_demo_preferences ) ? $borges_demo_preferences : array();\n\n$borges_demo_preferences['core/edit-post'] = array_merge(\n\tisset( $borges_demo_preferences['core/edit-post'] ) && is_array( $borges_demo_preferences['core/edit-post'] )\n\t\t? $borges_demo_preferences['core/edit-post']\n\t\t: array(),\n\tarray( 'welcomeGuide' => false )\n);\n$borges_demo_preferences['_modified'] = gmdate( 'c' );\n\nupdate_user_meta( 1, $borges_demo_meta_key, $borges_demo_preferences );\n" } ] } diff --git a/playground/demo-content.json b/playground/demo-content.json new file mode 100644 index 0000000..0272632 --- /dev/null +++ b/playground/demo-content.json @@ -0,0 +1,280 @@ +{ + "$comment": "Source for the Playground demo page. Run `npm run playground:build` after editing; it inlines this file and demo-content.php into every demo blueprint. src/playground-demo.test.js checks that each sample reaches the parser backend named by its `format`, and that each example's `exports` match what the editor computes for Cite / Export.", + "title": "Borges Bibliography Builder demo", + "sampleGroups": [ + { + "title": "DOIs", + "description": "A bare DOI, a doi: prefix, or a doi.org link. Metadata comes from Crossref.", + "samples": [ + { "format": "doi", "input": "10.1038/s41586-020-2649-2" }, + { "format": "doi", "input": "doi:10.1038/nature14539" }, + { "format": "doi", "input": "https://doi.org/10.1145/3368089.3409742" } + ] + }, + { + "title": "PubMed and PubMed Central", + "description": "A PMID needs its PMID: label. A PMCID starts with PMC, with or without a PMCID: label.", + "samples": [ + { "format": "pmid", "input": "PMID:26673779" }, + { "format": "pmcid", "input": "PMC3531190" } + ] + }, + { + "title": "arXiv", + "description": "An arXiv: ID (modern or legacy), an arxiv.org link, or an arXiv DOI.", + "samples": [ + { "format": "arxiv", "input": "arXiv:1706.03762" }, + { "format": "arxiv", "input": "https://arxiv.org/abs/1810.04805" }, + { "format": "arxiv", "input": "arXiv:hep-th/9711200" }, + { "format": "arxiv", "input": "10.48550/arXiv.2005.14165" } + ] + }, + { + "title": "ISBNs", + "description": "A labeled ISBN-13 or ISBN-10, or a bare ISBN-13 starting with 978 or 979. Books come from Open Library, with Google Books as a fallback.", + "samples": [ + { "format": "isbn", "input": "ISBN 978-0-14-032872-1" }, + { "format": "isbn", "input": "9780262033848" }, + { "format": "isbn", "input": "ISBN-10: 0-8112-0012-4" } + ] + }, + { + "title": "BibTeX and BibLaTeX", + "description": "Whole entries, one or many. BibLaTeX fields such as date and location are understood.", + "samples": [ + { + "format": "bibtex", + "input": "@article{knuth1984literate,\n author = {Knuth, Donald E.},\n title = {Literate Programming},\n journal = {The Computer Journal},\n year = {1984},\n volume = {27},\n number = {2},\n pages = {97--111},\n doi = {10.1093/comjnl/27.2.97}\n}" + }, + { + "format": "bibtex", + "input": "@book{borges1962labyrinths,\n author = {Borges, Jorge Luis},\n title = {Labyrinths: Selected Stories and Other Writings},\n editor = {Yates, Donald A. and Irby, James E.},\n date = {1962},\n publisher = {New Directions},\n location = {New York}\n}" + } + ] + }, + { + "title": "Formatted citations", + "description": "Plain-text citations for books, journal articles, chapters, and webpages, in Chicago or APA shape. A citation that contains a DOI, PMID, PMCID, or arXiv ID is looked up by that identifier instead.", + "samples": [ + { + "format": "freetext", + "input": "Binder, Amy J., and Jeffrey L. Kidder. The Channels of Student Activism: How the Left and Right Are Winning (and Losing) in Campus Politics Today. University of Chicago Press, 2022." + }, + { + "format": "freetext", + "input": "Dittmar, Emily L., and Douglas W. Schemske. “Temporal Variation in Selection Influences Microgeographic Local Adaptation.” American Naturalist 202, no. 4 (2023): 471–85." + }, + { + "format": "freetext", + "input": "Doyle, Kathleen. “The Queen Mary Psalter.” In The Book by Design: The Remarkable Story of the World’s Greatest Invention, edited by P. J. M. Marks and Stephen Parkin. University of Chicago Press, 2023." + }, + { + "format": "freetext", + "input": "Yale University. “About Yale: Yale Facts.” Accessed March 8, 2022. https://www.yale.edu/about-yale/yale-facts." + }, + { + "format": "doi", + "embedded": true, + "input": "Dittmar, Emily L., and Douglas W. Schemske. “Temporal Variation in Selection Influences Microgeographic Local Adaptation.” American Naturalist 202, no. 4 (2023): 471–85. https://doi.org/10.1086/725865." + } + ] + } + ], + "features": [ + "Citation Style (block sidebar): switch among Chicago, APA, MLA, Harvard, IEEE, Vancouver, OSCOLA, ABNT, and more. Generated text reformats; your manual edits are kept.", + "Edit an entry: select a citation and use its edit button to correct fields. Reset edits restores the generated text.", + "Manual Entry: next to Paste / Import, add a citation field by field when there is nothing to paste.", + "Duplicates: paste a DOI that is already in the list; it is skipped, not added twice.", + "Order: notes and author-date styles sort alphabetically. Numeric styles (IEEE, Vancouver) keep your order; use Move citation up/down.", + "Exports (block toolbar): copy the bibliography as text, or download BibTeX, BibLaTeX, RIS, or CSL-JSON.", + "Per-entry Cite / Export (sidebar): gives readers copy and download links under each entry on the published page.", + "Machine-readable output (sidebar): JSON-LD for search engines (on by default), COinS for Zotero, and embedded CSL-JSON.", + "Front end: use View Post to see the published bibliography, then open its read-only REST data." + ], + "examples": [ + { + "title": "Chicago notes-bibliography, with Cite / Export", + "description": "A book, a chapter, a journal article, and a webpage, sorted alphabetically. Per-entry Cite / Export and COinS are on; view the post to try the copy and download links.", + "attributes": { + "bibliographyId": "demo-chicago-notes", + "citationStyle": "chicago-notes-bibliography", + "headingText": "Works Cited", + "outputJsonLd": true, + "outputCoins": true, + "outputCslJson": false, + "outputCiteExport": true + }, + "citations": [ + { + "type": "book", + "title": "Labyrinths: Selected Stories and Other Writings", + "author": [{ "family": "Borges", "given": "Jorge Luis" }], + "editor": [ + { "family": "Yates", "given": "Donald A." }, + { "family": "Irby", "given": "James E." } + ], + "publisher": "New Directions", + "publisher-place": "New York", + "issued": { "date-parts": [[1962]] } + }, + { + "type": "chapter", + "title": "The Library of Babel", + "author": [{ "family": "Borges", "given": "Jorge Luis" }], + "translator": [{ "family": "Irby", "given": "James E." }], + "container-title": "Labyrinths: Selected Stories and Other Writings", + "editor": [ + { "family": "Yates", "given": "Donald A." }, + { "family": "Irby", "given": "James E." } + ], + "publisher": "New Directions", + "publisher-place": "New York", + "issued": { "date-parts": [[1962]] } + }, + { + "type": "article-journal", + "title": "Literate Programming", + "author": [{ "family": "Knuth", "given": "Donald E." }], + "container-title": "The Computer Journal", + "volume": "27", + "issue": "2", + "page": "97-111", + "DOI": "10.1093/comjnl/27.2.97", + "issued": { "date-parts": [[1984]] } + }, + { + "type": "webpage", + "title": "Block Editor Handbook", + "author": [{ "literal": "WordPress.org" }], + "container-title": "WordPress Developer Resources", + "URL": "https://developer.wordpress.org/block-editor/" + } + ], + "exports": [ + { + "exportBibtex": "@book{Borges1962Labyrinths,\n\taddress = {New York},\n\tauthor = {Borges, Jorge Luis},\n\teditor = {Yates, Donald A. and Irby, James E.},\n\tyear = {1962},\n\tpublisher = {New Directions},\n\ttitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\n}\n\n\n", + "exportBiblatex": "@book{Borges1962Labyrinths,\n\tauthor = {Borges, Jorge Luis},\n\teditor = {Yates, Donald A. and Irby, James E.},\n\tdate = {1962},\n\tlocation = {New York},\n\tpublisher = {New Directions},\n\ttitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\n}\n\n\n" + }, + { + "exportBibtex": "@inbook{Borges1962Library,\n\taddress = {New York},\n\tauthor = {Borges, Jorge Luis},\n\tbooktitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\n\teditor = {Yates, Donald A. and Irby, James E.},\n\tyear = {1962},\n\tpublisher = {New Directions},\n\ttitle = {The {Library} of {Babel}},\n}\n\n\n", + "exportBiblatex": "@inbook{Borges1962Library,\n\tauthor = {Borges, Jorge Luis},\n\tbooktitle = {Labyrinths: Selected {Stories} and {Other} {Writings}},\n\teditor = {Yates, Donald A. and Irby, James E.},\n\tdate = {1962},\n\tlocation = {New York},\n\tpublisher = {New Directions},\n\ttitle = {The {Library} of {Babel}},\n\ttranslator = {Irby, James E.},\n}\n\n\n" + }, + { + "exportBibtex": "@article{Knuth1984Literate,\n\tauthor = {Knuth, Donald E.},\n\tjournal = {The Computer Journal},\n\tdoi = {10.1093/comjnl/27.2.97},\n\tnumber = {2},\n\tyear = {1984},\n\tpages = {97--111},\n\ttitle = {Literate {Programming}},\n\tvolume = {27},\n}\n\n\n", + "exportBiblatex": "@article{Knuth1984Literate,\n\tauthor = {Knuth, Donald E.},\n\tjournaltitle = {The Computer Journal},\n\tdoi = {10.1093/comjnl/27.2.97},\n\tnumber = {2},\n\tdate = {1984},\n\tpages = {97--111},\n\ttitle = {Literate {Programming}},\n\tvolume = {27},\n}\n\n\n" + }, + { + "exportBibtex": "@misc{WordPressBlock,\n\tauthor = {{WordPress.org}},\n\ttitle = {Block {Editor} {Handbook}},\n\turl = {https://developer.wordpress.org/block-editor/},\n\thowpublished = {https://developer.wordpress.org/block-editor/},\n}\n\n\n", + "exportBiblatex": "@online{WordPressBlock,\n\tauthor = {{WordPress.org}},\n\tbooktitle = {WordPress {Developer} {Resources}},\n\ttitle = {Block {Editor} {Handbook}},\n\turl = {https://developer.wordpress.org/block-editor/},\n}\n\n\n" + } + ] + }, + { + "title": "APA 7 author-date", + "description": "Journal articles and an arXiv preprint in APA style, with DOIs as links.", + "attributes": { + "bibliographyId": "demo-apa-7", + "citationStyle": "apa-7", + "headingText": "References", + "outputJsonLd": true, + "outputCoins": false, + "outputCslJson": false, + "outputCiteExport": false + }, + "citations": [ + { + "type": "article-journal", + "title": "Deep learning", + "author": [ + { "family": "LeCun", "given": "Yann" }, + { "family": "Bengio", "given": "Yoshua" }, + { "family": "Hinton", "given": "Geoffrey" } + ], + "container-title": "Nature", + "volume": "521", + "issue": "7553", + "page": "436-444", + "DOI": "10.1038/nature14539", + "issued": { "date-parts": [[2015]] } + }, + { + "type": "article", + "title": "Attention is all you need", + "author": [ + { "family": "Vaswani", "given": "Ashish" }, + { "family": "Shazeer", "given": "Noam" }, + { "family": "Parmar", "given": "Niki" }, + { "family": "Uszkoreit", "given": "Jakob" }, + { "family": "Jones", "given": "Llion" }, + { "family": "Gomez", "given": "Aidan N." }, + { "family": "Kaiser", "given": "Łukasz" }, + { "family": "Polosukhin", "given": "Illia" } + ], + "publisher": "arXiv", + "number": "1706.03762", + "DOI": "10.48550/arXiv.1706.03762", + "issued": { "date-parts": [[2017]] } + }, + { + "type": "article-journal", + "title": "Computing machinery and intelligence", + "author": [{ "family": "Turing", "given": "A. M." }], + "container-title": "Mind", + "volume": "59", + "issue": "236", + "page": "433-460", + "DOI": "10.1093/mind/LIX.236.433", + "issued": { "date-parts": [[1950]] } + } + ] + }, + { + "title": "IEEE numeric", + "description": "Numbered entries keep the order you give them. Select the block and use Move citation up/down to renumber.", + "attributes": { + "bibliographyId": "demo-ieee", + "citationStyle": "ieee", + "headingText": "References", + "outputJsonLd": true, + "outputCoins": false, + "outputCslJson": false, + "outputCiteExport": false + }, + "citations": [ + { + "type": "article-journal", + "title": "A Mathematical Theory of Communication", + "author": [{ "family": "Shannon", "given": "Claude E." }], + "container-title": "Bell System Technical Journal", + "volume": "27", + "issue": "3", + "page": "379-423", + "DOI": "10.1002/j.1538-7305.1948.tb01338.x", + "issued": { "date-parts": [[1948]] } + }, + { + "type": "article-journal", + "title": "Computing Machinery and Intelligence", + "author": [{ "family": "Turing", "given": "A. M." }], + "container-title": "Mind", + "volume": "59", + "issue": "236", + "page": "433-460", + "DOI": "10.1093/mind/LIX.236.433", + "issued": { "date-parts": [[1950]] } + }, + { + "type": "article-journal", + "title": "Literate Programming", + "author": [{ "family": "Knuth", "given": "Donald E." }], + "container-title": "The Computer Journal", + "volume": "27", + "issue": "2", + "page": "97-111", + "DOI": "10.1093/comjnl/27.2.97", + "issued": { "date-parts": [[1984]] } + } + ] + } + ] +} diff --git a/playground/demo-content.php b/playground/demo-content.php new file mode 100644 index 0000000..ea1b235 --- /dev/null +++ b/playground/demo-content.php @@ -0,0 +1,204 @@ + $name, + 'attrs' => $attrs, + 'innerBlocks' => array(), + 'innerHTML' => $html, + 'innerContent' => array( $html ), + ) + ) . "\n\n"; +}; + +$borges_demo_paragraph = static function ( $html ) use ( $borges_demo_block ) { + return $borges_demo_block( 'core/paragraph', array(), '

' . $html . '

' ); +}; + +$borges_demo_heading = static function ( $text, $level ) use ( $borges_demo_block ) { + $attrs = 2 === $level ? array() : array( 'level' => $level ); + $tag = 'h' . $level; + + return $borges_demo_block( 'core/heading', $attrs, '<' . $tag . ' class="wp-block-heading">' . esc_html( $text ) . '' ); +}; + +$borges_demo_code = static function ( $text ) use ( $borges_demo_block ) { + return $borges_demo_block( + 'core/code', + array(), + '
' . htmlspecialchars( $text, ENT_NOQUOTES, 'UTF-8' ) . '
' + ); +}; + +$borges_demo_list = static function ( $items ) { + $html = "\n\n\n\n"; +}; + +$borges_demo_can_prebuild = function_exists( 'bibliography_builder_render_save_markup' ) + && function_exists( 'bibliography_builder_format_csl_items' ) + && bibliography_builder_can_render_save_markup(); + +// Returns the serialized block, or '' when this install cannot build it. +$borges_demo_bibliography = static function ( $example ) use ( $borges_demo_can_prebuild ) { + if ( ! $borges_demo_can_prebuild ) { + return ''; + } + + $csl_items = bibliography_builder_validate_and_sanitize_csl_items( $example['citations'] ); + + if ( is_wp_error( $csl_items ) ) { + return ''; + } + + $formatted = bibliography_builder_format_csl_items( $csl_items, $example['attributes']['citationStyle'] ); + + if ( is_wp_error( $formatted ) ) { + return ''; + } + + $citations = array(); + + foreach ( array_values( $csl_items ) as $index => $csl ) { + $citation = array( + 'id' => $example['attributes']['bibliographyId'] . '-' . ( $index + 1 ), + 'csl' => $csl, + 'formattedText' => $formatted[ $index ], + ); + + // The editor stores these for Cite / Export; without them it adds + // them on load and the untouched demo opens with unsaved changes. + foreach ( array( 'exportBibtex', 'exportBiblatex' ) as $export_key ) { + if ( isset( $example['exports'][ $index ][ $export_key ] ) ) { + $citation[ $export_key ] = $example['exports'][ $index ][ $export_key ]; + } + } + + $citations[] = $citation; + } + + $attrs = $example['attributes']; + // Stored in display order, as the editor stores them. + $attrs['citations'] = bibliography_builder_sort_citations_for_save( $citations, $attrs['citationStyle'] ); + // Render from the attributes as the editor will parse them back. + $html = bibliography_builder_render_save_markup( + bibliography_builder_decode_save_attributes( wp_json_encode( $attrs ) ) + ); + + if ( '' === $html ) { + return ''; + } + + return serialize_block( + array( + 'blockName' => 'bibliography-builder/bibliography', + 'attrs' => $attrs, + 'innerBlocks' => array(), + 'innerHTML' => $html, + 'innerContent' => array( $html ), + ) + ) . "\n\n"; +}; + +$content = $borges_demo_paragraph( $borges_demo_intro ); + +$content .= $borges_demo_heading( '1. Paste any supported format', 2 ); +$content .= $borges_demo_paragraph( + 'Select the empty Bibliography block at the end of this section, open Paste / Import, paste one or more samples, and choose Add. Separate entries with a blank line; up to 50 go in one paste, so you can copy several groups at once.' +); + +foreach ( $borges_demo['sampleGroups'] as $group ) { + $content .= $borges_demo_heading( $group['title'], 3 ); + $content .= $borges_demo_paragraph( $group['description'] ); + $content .= $borges_demo_code( implode( "\n\n", wp_list_pluck( $group['samples'], 'input' ) ) ); +} + +$content .= $borges_demo_block( + 'bibliography-builder/bibliography', + array( + 'bibliographyId' => 'demo-try-it', + 'headingText' => 'References', + ), + '' +); + +$content .= $borges_demo_heading( '2. Key features to try', 2 ); +$content .= $borges_demo_list( $borges_demo['features'] ); + +$content .= $borges_demo_heading( '3. Example bibliographies', 2 ); + +$borges_demo_examples = ''; + +foreach ( $borges_demo['examples'] as $example ) { + $block = $borges_demo_bibliography( $example ); + + if ( '' === $block ) { + continue; + } + + $borges_demo_examples .= $borges_demo_heading( $example['title'], 3 ); + $borges_demo_examples .= $borges_demo_paragraph( $example['description'] ); + $borges_demo_examples .= $block; +} + +$content .= '' !== $borges_demo_examples + ? $borges_demo_paragraph( 'Each block below was built from CSL-JSON. Select one to change its style or settings.' ) . $borges_demo_examples + : $borges_demo_paragraph( 'Pre-built examples need Borges 1.7.0 or later. Paste a few samples from section 1 to build your own.' ); + +wp_update_post( + array( + 'ID' => 1, + 'post_title' => $borges_demo['title'], + 'post_content' => wp_slash( $content ), + ) +); + +// Skip the block editor's Welcome Guide so the demo page is what opens. +$borges_demo_meta_key = $GLOBALS['wpdb']->get_blog_prefix() . 'persisted_preferences'; +$borges_demo_preferences = get_user_meta( 1, $borges_demo_meta_key, true ); +$borges_demo_preferences = is_array( $borges_demo_preferences ) ? $borges_demo_preferences : array(); + +$borges_demo_preferences['core/edit-post'] = array_merge( + isset( $borges_demo_preferences['core/edit-post'] ) && is_array( $borges_demo_preferences['core/edit-post'] ) + ? $borges_demo_preferences['core/edit-post'] + : array(), + array( 'welcomeGuide' => false ) +); +$borges_demo_preferences['_modified'] = gmdate( 'c' ); + +update_user_meta( 1, $borges_demo_meta_key, $borges_demo_preferences ); diff --git a/scripts/build-playground-blueprints.js b/scripts/build-playground-blueprints.js new file mode 100644 index 0000000..72561bd --- /dev/null +++ b/scripts/build-playground-blueprints.js @@ -0,0 +1,117 @@ +#!/usr/bin/env node +/** + * Builds the demo-page step of every Playground demo blueprint. + * + * The demo page is defined once, in playground/demo-content.json (samples, + * feature notes, example bibliographies) and playground/demo-content.php (the + * script that writes it). Playground blueprints cannot load repository files, + * so this inlines both into each blueprint's runPHP step. + * + * npm run playground:build rewrite the blueprints + * npm run playground:build -- --check fail if any blueprint is stale + */ + +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '..'); + +const RELEASE_INTRO = + 'Welcome to Borges Bibliography Builder. This Playground runs the latest release. It is a sandbox: nothing here is kept after you close the tab.'; + +const DEMO_BLUEPRINTS = [ + { file: 'playground/blueprint.json', intro: RELEASE_INTRO }, + { + file: 'playground/blueprint-main.json', + intro: 'Welcome to the Borges Bibliography Builder development build. This Playground runs the current main branch, not a stable release. It is a sandbox: nothing here is kept after you close the tab.', + }, + { file: '.wordpress-org/blueprints/blueprint.json', intro: RELEASE_INTRO }, +]; + +function phpSingleQuoted(value) { + return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`; +} + +/** + * The runPHP code for one blueprint. + * + * @param {string} intro Lead paragraph HTML. + * @return {string} PHP source. + */ +function buildDemoCode(intro) { + const data = JSON.parse( + fs.readFileSync(path.join(ROOT, 'playground/demo-content.json'), 'utf8') + ); + delete data.$comment; + + const json = JSON.stringify(data); + if (/^JSON;?$/mu.test(json)) { + throw new Error('demo-content.json would end the PHP nowdoc early.'); + } + + const script = fs + .readFileSync(path.join(ROOT, 'playground/demo-content.php'), 'utf8') + .replace(/^<\?php\n/u, ''); + + return [ + '} Blueprints. + */ +function buildBlueprints() { + return DEMO_BLUEPRINTS.map(({ file, intro }) => { + const absolute = path.join(ROOT, file); + const current = fs.readFileSync(absolute, 'utf8'); + const blueprint = JSON.parse(current); + const steps = blueprint.steps.filter((step) => step.step !== 'runPHP'); + + steps.push({ step: 'runPHP', code: buildDemoCode(intro) }); + blueprint.steps = steps; + + return { + file, + current, + expected: `${JSON.stringify(blueprint, null, '\t')}\n`, + }; + }); +} + +if (require.main === module) { + const check = process.argv.includes('--check'); + const stale = []; + + for (const { file, current, expected } of buildBlueprints()) { + if (current === expected) { + continue; + } + + if (check) { + stale.push(file); + } else { + fs.writeFileSync(path.join(ROOT, file), expected); + process.stdout.write(`updated ${file}\n`); + } + } + + if (stale.length) { + process.stderr.write( + `Stale Playground blueprints (run npm run playground:build): ${stale.join( + ', ' + )}\n` + ); + process.exit(1); + } +} + +module.exports = { buildBlueprints, DEMO_BLUEPRINTS }; diff --git a/src/playground-demo.test.js b/src/playground-demo.test.js new file mode 100644 index 0000000..1159ef2 --- /dev/null +++ b/src/playground-demo.test.js @@ -0,0 +1,136 @@ +/** + * The Playground demo page (playground/demo-content.json) promises that each + * sample works and that the example bibliographies open without unsaved + * changes. These tests hold it to that, and keep the generated blueprints in + * sync with their source. + */ + +import { parsePastedInput, extractEmbeddedIdentifier } from './lib/parser'; +import { computeExportStrings } from './hooks/compute-export-strings'; + +// No network: every resolver fails, so an identifier sample surfaces the error +// of the backend it was routed to. BibTeX and free text parse locally. +jest.mock('@wordpress/api-fetch', () => + jest.fn(() => Promise.reject(new Error('offline'))) +); +jest.mock('@citation-js/plugin-doi', () => ({})); + +const demo = require('../playground/demo-content.json'); +const { buildBlueprints } = require('../scripts/build-playground-blueprints'); + +const offlineFetch = () => Promise.reject(new Error('offline')); + +const BACKEND_ERRORS = { + doi: /DOI/, + pmid: /PMID/, + pmcid: /PMCID/, + arxiv: /arXiv/, + isbn: /ISBN/, +}; + +const samples = demo.sampleGroups.flatMap((group) => + group.samples.map((sample) => [group.title, sample]) +); + +describe('Playground demo samples', () => { + it('covers every supported input format', () => { + const formats = new Set(samples.map(([, sample]) => sample.format)); + + expect([...formats].sort()).toEqual( + [ + 'arxiv', + 'bibtex', + 'doi', + 'freetext', + 'isbn', + 'pmcid', + 'pmid', + ].sort() + ); + }); + + it('fits in a single paste', () => { + expect(samples.length).toBeLessThanOrEqual(50); + }); + + const LOCAL_FORMATS = ['bibtex', 'freetext']; + + it.each( + samples.filter( + ([, s]) => !s.embedded && LOCAL_FORMATS.includes(s.format) + ) + )('%s: %p parses locally', async (group, { format, input }) => { + const result = await parsePastedInput(input, undefined, { + fetchFn: offlineFetch, + }); + + expect(result.errors).toEqual([]); + expect(result.entries).toHaveLength(1); + expect(result.entries[0].inputFormat).toBe(format); + }); + + it.each( + samples.filter( + ([, s]) => !s.embedded && !LOCAL_FORMATS.includes(s.format) + ) + )('%s: %p reaches its resolver', async (group, { format, input }) => { + const result = await parsePastedInput(input, undefined, { + fetchFn: offlineFetch, + }); + + expect(result.entries).toEqual([]); + expect(result.errors).toHaveLength(1); + expect(result.errors[0]).toMatch(BACKEND_ERRORS[format]); + }); + + it.each(samples.filter(([, s]) => s.embedded))( + '%s: %p is looked up by its embedded identifier', + async (group, { format, input }) => { + expect(extractEmbeddedIdentifier(input)).toMatchObject({ format }); + + // Offline, the lookup fails and the citation text still parses. + const result = await parsePastedInput(input, undefined, { + fetchFn: offlineFetch, + }); + expect(result.errors).toEqual([]); + expect(result.entries).toHaveLength(1); + } + ); +}); + +describe('Playground demo examples', () => { + const withCiteExport = demo.examples.filter( + (example) => example.attributes.outputCiteExport + ); + + it('has an example with Cite / Export', () => { + expect(withCiteExport.length).toBeGreaterThan(0); + }); + + it.each(withCiteExport.map((example) => [example.title, example]))( + '%s stores the export strings the editor computes', + async (title, example) => { + expect(example.exports).toEqual( + await computeExportStrings( + example.citations, + example.attributes.citationStyle + ) + ); + } + ); + + it('stores no export strings where Cite / Export is off', () => { + demo.examples + .filter((example) => !example.attributes.outputCiteExport) + .forEach((example) => expect(example.exports).toBeUndefined()); + }); +}); + +describe('Playground demo blueprints', () => { + it.each(buildBlueprints().map((blueprint) => [blueprint.file, blueprint]))( + '%s is built from the current demo content (npm run playground:build)', + (file, { current, expected }) => { + expect(current).toBe(expected); + } + ); +});