diff --git a/css/components/page-parts/_navbar.scss b/css/components/page-parts/_navbar.scss index 6cc0d553e..35d747440 100644 --- a/css/components/page-parts/_navbar.scss +++ b/css/components/page-parts/_navbar.scss @@ -132,6 +132,36 @@ $center-content: true !default; @include dialogs.ptx-dialog-modal; } + .ptx-code-cells-popup { + padding: 10px; + margin-top: $nav-height; + min-width: 16rem; + max-width: 600px; + margin-bottom: 0.5em; + width: 90%; + + @include dialogs.ptx-dialog-modal; + + .ptx-code-cells-popup-controls { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; + margin-bottom: 1em; + border-bottom: 1px solid var(--dialog-border-color); + } + + .ptx-code-cells-actions { + display: flex; + gap: 10px; + } + + .ptx-code-cells-status { + margin-top: 1em; + min-height: 1.2em; + } + } + .ptx-readability-options-popup-controls { display: flex; gap: 10px; @@ -200,7 +230,7 @@ $center-content: true !default; display: none; } - :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button) .name { + :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button, .ptx-code-cells-button) .name { @include accessibility.sr-only; } @@ -290,6 +320,13 @@ $center-content: true !default; bottom: $nav-height; } + .ptx-code-cells-popup { + margin-top: unset; + margin-bottom: 0; + top: auto; + bottom: $nav-height; + } + .ptx-dropdown-content { top: unset; bottom: $nav-height; @@ -299,7 +336,7 @@ $center-content: true !default; display: none; } - :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button) .name { + :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button, .ptx-code-cells-button) .name { display: none; } } diff --git a/css/dist/theme-boulder.css b/css/dist/theme-boulder.css index 786be3079..0541d4cda 100644 --- a/css/dist/theme-boulder.css +++ b/css/dist/theme-boulder.css @@ -3072,6 +3072,86 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { background: var(--dialog-background); opacity: 0.6; } +.ptx-navbar .ptx-code-cells-popup { + padding: 10px; + margin-top: 36px; + min-width: 16rem; + max-width: 600px; + margin-bottom: 0.5em; + width: 90%; + border: 1px solid var(--dialog-border-color); + border-radius: 0; + background: var(--dialog-background); + box-shadow: 0 0 5px var(--dialog-shadow-color); + color: var(--dialog-text-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button { + font: inherit; + display: flex; + justify-content: center; + align-items: center; + padding: 0 10px; + gap: 10px; + min-height: 34px; + color: var(--button-text-color); + background-color: var(--button-background); + border-width: 0; + border-color: var(--button-border-color); + border-style: solid; + border-radius: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:hover:not(.disabled) { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:focus-visible { + outline: 2px solid var(--button-text-color); + outline-offset: -2px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.disabled { + opacity: 0.4; + cursor: not-allowed; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.hidden { + display: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.open { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-topbar { + min-height: 15px; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .heading:is(h2) { + font-size: 1.5em; + margin: 0.5em 0; +} +.ptx-navbar .ptx-code-cells-popup::backdrop { + background: var(--dialog-background); + opacity: 0.6; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-popup-controls { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; + margin-bottom: 1em; + border-bottom: 1px solid var(--dialog-border-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-actions { + display: flex; + gap: 10px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-status { + margin-top: 1em; + min-height: 1.2em; +} .ptx-navbar .ptx-readability-options-popup-controls { display: flex; gap: 10px; @@ -3127,7 +3207,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar :is(.index-button) .icon { display: none; } -.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button) .name { +.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button, .ptx-code-cells-button) .name { clip: rect(1px, 1px, 1px, 1px); clip-path: inset(50%); height: 1px; @@ -3242,6 +3322,12 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { top: auto; bottom: 36px; } + .ptx-navbar .ptx-code-cells-popup { + margin-top: unset; + margin-bottom: 0; + top: auto; + bottom: 36px; + } .ptx-navbar .ptx-dropdown-content { top: unset; bottom: 36px; @@ -3249,7 +3335,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar .ptx-embed-button { display: none; } - .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button) .name { + .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button, .ptx-code-cells-button) .name { display: none; } } diff --git a/css/dist/theme-default-modern.css b/css/dist/theme-default-modern.css index a38a15af8..be643ea9c 100644 --- a/css/dist/theme-default-modern.css +++ b/css/dist/theme-default-modern.css @@ -665,6 +665,86 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { background: var(--dialog-background); opacity: 0.6; } +.ptx-navbar .ptx-code-cells-popup { + padding: 10px; + margin-top: 36px; + min-width: 16rem; + max-width: 600px; + margin-bottom: 0.5em; + width: 90%; + border: 1px solid var(--dialog-border-color); + border-radius: 0; + background: var(--dialog-background); + box-shadow: 0 0 5px var(--dialog-shadow-color); + color: var(--dialog-text-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button { + font: inherit; + display: flex; + justify-content: center; + align-items: center; + padding: 0 10px; + gap: 10px; + min-height: 34px; + color: var(--button-text-color); + background-color: var(--button-background); + border-width: 0; + border-color: var(--button-border-color); + border-style: solid; + border-radius: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:hover:not(.disabled) { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:focus-visible { + outline: 2px solid var(--button-text-color); + outline-offset: -2px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.disabled { + opacity: 0.4; + cursor: not-allowed; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.hidden { + display: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.open { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-topbar { + min-height: 15px; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .heading:is(h2) { + font-size: 1.5em; + margin: 0.5em 0; +} +.ptx-navbar .ptx-code-cells-popup::backdrop { + background: var(--dialog-background); + opacity: 0.6; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-popup-controls { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; + margin-bottom: 1em; + border-bottom: 1px solid var(--dialog-border-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-actions { + display: flex; + gap: 10px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-status { + margin-top: 1em; + min-height: 1.2em; +} .ptx-navbar .ptx-readability-options-popup-controls { display: flex; gap: 10px; @@ -720,7 +800,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar :is(.index-button) .icon { display: none; } -.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button) .name { +.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button, .ptx-code-cells-button) .name { clip: rect(1px, 1px, 1px, 1px); clip-path: inset(50%); height: 1px; @@ -835,6 +915,12 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { top: auto; bottom: 36px; } + .ptx-navbar .ptx-code-cells-popup { + margin-top: unset; + margin-bottom: 0; + top: auto; + bottom: 36px; + } .ptx-navbar .ptx-dropdown-content { top: unset; bottom: 36px; @@ -842,7 +928,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar .ptx-embed-button { display: none; } - .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button) .name { + .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button, .ptx-code-cells-button) .name { display: none; } } diff --git a/css/dist/theme-denver.css b/css/dist/theme-denver.css index 9ac2180ae..e9e727aeb 100644 --- a/css/dist/theme-denver.css +++ b/css/dist/theme-denver.css @@ -669,6 +669,86 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { background: var(--dialog-background); opacity: 0.6; } +.ptx-navbar .ptx-code-cells-popup { + padding: 10px; + margin-top: 36px; + min-width: 16rem; + max-width: 600px; + margin-bottom: 0.5em; + width: 90%; + border: 1px solid var(--dialog-border-color); + border-radius: 0; + background: var(--dialog-background); + box-shadow: 0 0 5px var(--dialog-shadow-color); + color: var(--dialog-text-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button { + font: inherit; + display: flex; + justify-content: center; + align-items: center; + padding: 0 10px; + gap: 10px; + min-height: 34px; + color: var(--button-text-color); + background-color: var(--button-background); + border-width: 0; + border-color: var(--button-border-color); + border-style: solid; + border-radius: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:hover:not(.disabled) { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:focus-visible { + outline: 2px solid var(--button-text-color); + outline-offset: -2px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.disabled { + opacity: 0.4; + cursor: not-allowed; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.hidden { + display: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.open { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-topbar { + min-height: 15px; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .heading:is(h2) { + font-size: 1.5em; + margin: 0.5em 0; +} +.ptx-navbar .ptx-code-cells-popup::backdrop { + background: var(--dialog-background); + opacity: 0.6; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-popup-controls { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; + margin-bottom: 1em; + border-bottom: 1px solid var(--dialog-border-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-actions { + display: flex; + gap: 10px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-status { + margin-top: 1em; + min-height: 1.2em; +} .ptx-navbar .ptx-readability-options-popup-controls { display: flex; gap: 10px; @@ -724,7 +804,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar :is(.index-button) .icon { display: none; } -.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button) .name { +.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button, .ptx-code-cells-button) .name { clip: rect(1px, 1px, 1px, 1px); clip-path: inset(50%); height: 1px; @@ -839,6 +919,12 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { top: auto; bottom: 36px; } + .ptx-navbar .ptx-code-cells-popup { + margin-top: unset; + margin-bottom: 0; + top: auto; + bottom: 36px; + } .ptx-navbar .ptx-dropdown-content { top: unset; bottom: 36px; @@ -846,7 +932,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar .ptx-embed-button { display: none; } - .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button) .name { + .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button, .ptx-code-cells-button) .name { display: none; } } diff --git a/css/dist/theme-greeley.css b/css/dist/theme-greeley.css index 2a487620b..a6edc608d 100644 --- a/css/dist/theme-greeley.css +++ b/css/dist/theme-greeley.css @@ -3072,6 +3072,86 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { background: var(--dialog-background); opacity: 0.6; } +.ptx-navbar .ptx-code-cells-popup { + padding: 10px; + margin-top: 36px; + min-width: 16rem; + max-width: 600px; + margin-bottom: 0.5em; + width: 90%; + border: 1px solid var(--dialog-border-color); + border-radius: 0; + background: var(--dialog-background); + box-shadow: 0 0 5px var(--dialog-shadow-color); + color: var(--dialog-text-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button { + font: inherit; + display: flex; + justify-content: center; + align-items: center; + padding: 0 10px; + gap: 10px; + min-height: 34px; + color: var(--button-text-color); + background-color: var(--button-background); + border-width: 0; + border-color: var(--button-border-color); + border-style: solid; + border-radius: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:hover:not(.disabled) { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:focus-visible { + outline: 2px solid var(--button-text-color); + outline-offset: -2px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.disabled { + opacity: 0.4; + cursor: not-allowed; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.hidden { + display: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.open { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-topbar { + min-height: 15px; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .heading:is(h2) { + font-size: 1.5em; + margin: 0.5em 0; +} +.ptx-navbar .ptx-code-cells-popup::backdrop { + background: var(--dialog-background); + opacity: 0.6; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-popup-controls { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; + margin-bottom: 1em; + border-bottom: 1px solid var(--dialog-border-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-actions { + display: flex; + gap: 10px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-status { + margin-top: 1em; + min-height: 1.2em; +} .ptx-navbar .ptx-readability-options-popup-controls { display: flex; gap: 10px; @@ -3127,7 +3207,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar :is(.index-button) .icon { display: none; } -.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button) .name { +.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button, .ptx-code-cells-button) .name { clip: rect(1px, 1px, 1px, 1px); clip-path: inset(50%); height: 1px; @@ -3242,6 +3322,12 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { top: auto; bottom: 36px; } + .ptx-navbar .ptx-code-cells-popup { + margin-top: unset; + margin-bottom: 0; + top: auto; + bottom: 36px; + } .ptx-navbar .ptx-dropdown-content { top: unset; bottom: 36px; @@ -3249,7 +3335,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar .ptx-embed-button { display: none; } - .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button) .name { + .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button, .ptx-code-cells-button) .name { display: none; } } diff --git a/css/dist/theme-salem.css b/css/dist/theme-salem.css index 96085133c..95c8ebe0b 100644 --- a/css/dist/theme-salem.css +++ b/css/dist/theme-salem.css @@ -669,6 +669,86 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { background: var(--dialog-background); opacity: 0.6; } +.ptx-navbar .ptx-code-cells-popup { + padding: 10px; + margin-top: 36px; + min-width: 16rem; + max-width: 600px; + margin-bottom: 0.5em; + width: 90%; + border: 1px solid var(--dialog-border-color); + border-radius: 0; + background: var(--dialog-background); + box-shadow: 0 0 5px var(--dialog-shadow-color); + color: var(--dialog-text-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button { + font: inherit; + display: flex; + justify-content: center; + align-items: center; + padding: 0 10px; + gap: 10px; + min-height: 34px; + color: var(--button-text-color); + background-color: var(--button-background); + border-width: 0; + border-color: var(--button-border-color); + border-style: solid; + border-radius: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:hover:not(.disabled) { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:focus-visible { + outline: 2px solid var(--button-text-color); + outline-offset: -2px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.disabled { + opacity: 0.4; + cursor: not-allowed; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.hidden { + display: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.open { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-topbar { + min-height: 15px; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .heading:is(h2) { + font-size: 1.5em; + margin: 0.5em 0; +} +.ptx-navbar .ptx-code-cells-popup::backdrop { + background: var(--dialog-background); + opacity: 0.6; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-popup-controls { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; + margin-bottom: 1em; + border-bottom: 1px solid var(--dialog-border-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-actions { + display: flex; + gap: 10px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-status { + margin-top: 1em; + min-height: 1.2em; +} .ptx-navbar .ptx-readability-options-popup-controls { display: flex; gap: 10px; @@ -724,7 +804,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar :is(.index-button) .icon { display: none; } -.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button) .name { +.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button, .ptx-code-cells-button) .name { clip: rect(1px, 1px, 1px, 1px); clip-path: inset(50%); height: 1px; @@ -839,6 +919,12 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { top: auto; bottom: 36px; } + .ptx-navbar .ptx-code-cells-popup { + margin-top: unset; + margin-bottom: 0; + top: auto; + bottom: 36px; + } .ptx-navbar .ptx-dropdown-content { top: unset; bottom: 36px; @@ -846,7 +932,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar .ptx-embed-button { display: none; } - .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button) .name { + .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button, .ptx-code-cells-button) .name { display: none; } } diff --git a/css/dist/theme-tacoma.css b/css/dist/theme-tacoma.css index c53b9d871..995ae4149 100644 --- a/css/dist/theme-tacoma.css +++ b/css/dist/theme-tacoma.css @@ -669,6 +669,86 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { background: var(--dialog-background); opacity: 0.6; } +.ptx-navbar .ptx-code-cells-popup { + padding: 10px; + margin-top: 36px; + min-width: 16rem; + max-width: 600px; + margin-bottom: 0.5em; + width: 90%; + border: 1px solid var(--dialog-border-color); + border-radius: 0; + background: var(--dialog-background); + box-shadow: 0 0 5px var(--dialog-shadow-color); + color: var(--dialog-text-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button { + font: inherit; + display: flex; + justify-content: center; + align-items: center; + padding: 0 10px; + gap: 10px; + min-height: 34px; + color: var(--button-text-color); + background-color: var(--button-background); + border-width: 0; + border-color: var(--button-border-color); + border-style: solid; + border-radius: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:hover:not(.disabled) { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button:focus-visible { + outline: 2px solid var(--button-text-color); + outline-offset: -2px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.disabled { + opacity: 0.4; + cursor: not-allowed; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.hidden { + display: none; +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-close-button.open { + color: var(--button-hover-text-color); + background-color: var(--button-hover-background); +} +.ptx-navbar .ptx-code-cells-popup .ptx-dialog-topbar { + min-height: 15px; + user-select: none; +} +.ptx-navbar .ptx-code-cells-popup .heading:is(h2) { + font-size: 1.5em; + margin: 0.5em 0; +} +.ptx-navbar .ptx-code-cells-popup::backdrop { + background: var(--dialog-background); + opacity: 0.6; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-popup-controls { + display: flex; + gap: 10px; + align-items: center; + justify-content: space-between; + margin-bottom: 1em; + border-bottom: 1px solid var(--dialog-border-color); +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-actions { + display: flex; + gap: 10px; +} +.ptx-navbar .ptx-code-cells-popup .ptx-code-cells-status { + margin-top: 1em; + min-height: 1.2em; +} .ptx-navbar .ptx-readability-options-popup-controls { display: flex; gap: 10px; @@ -724,7 +804,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar :is(.index-button) .icon { display: none; } -.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button) .name { +.ptx-navbar :is(.runestone-profile, .activecode-toggle, .ptx-search-button, .ptx-calculator-toggle, .ptx-embed-button, .ptx-readability-options-button, .ptx-read-aloud-button, .ptx-code-cells-button) .name { clip: rect(1px, 1px, 1px, 1px); clip-path: inset(50%); height: 1px; @@ -839,6 +919,12 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { top: auto; bottom: 36px; } + .ptx-navbar .ptx-code-cells-popup { + margin-top: unset; + margin-bottom: 0; + top: auto; + bottom: 36px; + } .ptx-navbar .ptx-dropdown-content { top: unset; bottom: 36px; @@ -846,7 +932,7 @@ body.pretext.ptx-embedded .ptx-navbar .nav-other-controls > .button { .ptx-navbar .ptx-embed-button { display: none; } - .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button) .name { + .ptx-navbar :is(.toc-toggle, .previous-button, .up-button, .next-button, .ptx-calculator-toggle, .index-button, .ptx-embed-button, .ptx-readability-options-button, .ptx-code-cells-button) .name { display: none; } } diff --git a/js/dist/pretext-core.js b/js/dist/pretext-core.js index 8d7baa484..b742953ab 100644 --- a/js/dist/pretext-core.js +++ b/js/dist/pretext-core.js @@ -2955,6 +2955,104 @@ applyEmbedTheme(urlParams.get("embed")); }); + // ../../js/src/pretext-code-cells.js + function findCodeMirror(container) { + const wrapper = container.querySelector(".CodeMirror"); + return wrapper ? wrapper.CodeMirror : null; + } + function collectCodeCells() { + const cells = {}; + document.querySelectorAll(".ptx-sagecell").forEach((el2) => { + const cm = findCodeMirror(el2); + if (cm) { + cells[el2.id] = cm.getValue(); + } + }); + return cells; + } + function exportCodeCells(statusElement) { + const cells = collectCodeCells(); + const count = Object.keys(cells).length; + if (count === 0) { + statusElement.textContent = "No code cells found on this page."; + return; + } + const blob = new Blob([JSON.stringify(cells, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + const filename = window.location.pathname.split("/").pop().split(".").shift() || "pretext"; + link.href = url; + link.download = filename + "-code-cells.json"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + statusElement.textContent = "Exported " + count + " code cell" + (count === 1 ? "" : "s") + "."; + } + function importCodeCells(file, statusElement) { + const reader = new FileReader(); + reader.onload = () => { + let cells; + try { + cells = JSON.parse(reader.result); + } catch (err) { + statusElement.textContent = "Could not read that file: not valid JSON."; + return; + } + let updated = 0; + let missing = 0; + for (const [id, code] of Object.entries(cells)) { + const container = document.getElementById(id); + const cm = container ? findCodeMirror(container) : null; + if (cm) { + cm.setValue(code); + updated += 1; + } else { + missing += 1; + } + } + if (updated === 0) { + statusElement.textContent = "No matching code cells found on this page."; + } else if (missing === 0) { + statusElement.textContent = "Updated " + updated + " code cell" + (updated === 1 ? "" : "s") + "."; + } else { + statusElement.textContent = "Updated " + updated + " code cell" + (updated === 1 ? "" : "s") + "; " + missing + " not found on this page."; + } + }; + reader.onerror = () => { + statusElement.textContent = "Could not read that file."; + }; + reader.readAsText(file); + } + window.addEventListener("DOMContentLoaded", function() { + const codeButton = document.getElementById("ptx-code-cells-button"); + const codePopup = document.getElementById("ptx-code-cells-popup"); + if (!codeButton || !codePopup) { + return; + } + new PTXDialog(codePopup, codeButton, { + kind: "light-close", + closeButton: document.getElementById("ptx-code-cells-close-button") + }); + const statusElement = document.getElementById("ptx-code-cells-status"); + const importInput = document.getElementById("ptx-code-cells-import-input"); + const exportButton = document.getElementById("ptx-code-cells-export-button"); + if (exportButton) { + exportButton.addEventListener("click", () => exportCodeCells(statusElement)); + } + const importButton = document.getElementById("ptx-code-cells-import-button"); + if (importButton && importInput) { + importButton.addEventListener("click", () => importInput.click()); + importInput.addEventListener("change", () => { + const file = importInput.files[0]; + if (file) { + importCodeCells(file, statusElement); + } + importInput.value = ""; + }); + } + }); + // ../../js/src/pretext-core.js var import_knowl = __toESM(require_knowl()); })(); diff --git a/js/src/pretext-code-cells.js b/js/src/pretext-code-cells.js new file mode 100644 index 000000000..6c0501011 --- /dev/null +++ b/js/src/pretext-code-cells.js @@ -0,0 +1,119 @@ +// Import/export the contents of Sage code cells as a JSON file. +// +// Each cell is a "div.ptx-sagecell" with the stable id PreTeXt itself +// assigns (from the source's xml:id, or a generated one). SageMathCell's +// own runtime JavaScript (loaded externally, not part of this repo) adds +// an "output_" class to that same div once it initializes the cell -- +// but that hex string is a fresh random id generated on every page load, +// not something that survives a reload, so it can't be used as an export +// key. The PreTeXt id is what's stable, so the exported/imported JSON is +// keyed by that. Only the *code* currently in a cell's editor is touched, +// never its evaluated output. + +// CodeMirror 5 (what SageMathCell embeds) exposes the editor instance as a +// ".CodeMirror" property on the wrapper element it builds, nested inside +// the "div.ptx-sagecell" alongside the eval button and output area. +function findCodeMirror(container) { + const wrapper = container.querySelector(".CodeMirror"); + return wrapper ? wrapper.CodeMirror : null; +} + +function collectCodeCells() { + const cells = {}; + document.querySelectorAll(".ptx-sagecell").forEach((el) => { + const cm = findCodeMirror(el); + if (cm) { + cells[el.id] = cm.getValue(); + } + }); + return cells; +} + +function exportCodeCells(statusElement) { + const cells = collectCodeCells(); + const count = Object.keys(cells).length; + if (count === 0) { + statusElement.textContent = "No code cells found on this page."; + return; + } + const blob = new Blob([JSON.stringify(cells, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + const filename = window.location.pathname.split('/').pop().split('.').shift() || "pretext" + link.href = url; + link.download = filename + "-code-cells.json"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + statusElement.textContent = "Exported " + count + " code cell" + (count === 1 ? "" : "s") + "."; +} + +function importCodeCells(file, statusElement) { + const reader = new FileReader(); + reader.onload = () => { + let cells; + try { + cells = JSON.parse(reader.result); + } catch (err) { + statusElement.textContent = "Could not read that file: not valid JSON."; + return; + } + let updated = 0; + let missing = 0; + for (const [id, code] of Object.entries(cells)) { + const container = document.getElementById(id); + const cm = container ? findCodeMirror(container) : null; + if (cm) { + cm.setValue(code); + updated += 1; + } else { + missing += 1; + } + } + if (updated === 0) { + statusElement.textContent = "No matching code cells found on this page."; + } else if (missing === 0) { + statusElement.textContent = "Updated " + updated + " code cell" + (updated === 1 ? "" : "s") + "."; + } else { + statusElement.textContent = "Updated " + updated + " code cell" + (updated === 1 ? "" : "s") + "; " + missing + " not found on this page."; + } + }; + reader.onerror = () => { + statusElement.textContent = "Could not read that file."; + }; + reader.readAsText(file); +} + +window.addEventListener("DOMContentLoaded", function () { + const codeButton = document.getElementById("ptx-code-cells-button"); + const codePopup = document.getElementById("ptx-code-cells-popup"); + if (!codeButton || !codePopup) { + return; + } + + new PTXDialog(codePopup, codeButton, { + kind: "light-close", + closeButton: document.getElementById("ptx-code-cells-close-button") + }); + + const statusElement = document.getElementById("ptx-code-cells-status"); + const importInput = document.getElementById("ptx-code-cells-import-input"); + + const exportButton = document.getElementById("ptx-code-cells-export-button"); + if (exportButton) { + exportButton.addEventListener("click", () => exportCodeCells(statusElement)); + } + + const importButton = document.getElementById("ptx-code-cells-import-button"); + if (importButton && importInput) { + importButton.addEventListener("click", () => importInput.click()); + importInput.addEventListener("change", () => { + const file = importInput.files[0]; + if (file) { + importCodeCells(file, statusElement); + } + importInput.value = ""; + }); + } +}); diff --git a/js/src/pretext-core.js b/js/src/pretext-core.js index 6a76f8116..f6bb46335 100644 --- a/js/src/pretext-core.js +++ b/js/src/pretext-core.js @@ -21,4 +21,5 @@ import '../pretext.js'; import '../pretext_add_on.js'; import './pretext-printouts.js'; import './pretext-embed.js'; +import './pretext-code-cells.js'; import '../knowl.js'; diff --git a/xsl/localizations/en-US.xml b/xsl/localizations/en-US.xml index 0787b3a8d..d1f5309fd 100644 --- a/xsl/localizations/en-US.xml +++ b/xsl/localizations/en-US.xml @@ -351,6 +351,11 @@ along with PreTeXt. If not, see . Embed Embed this page Copy + + Code + Export or import the code in this page's cells as a JSON file. + Export + Import Profile diff --git a/xsl/pretext-html.xsl b/xsl/pretext-html.xsl index 60324da2b..0554f4515 100644 --- a/xsl/pretext-html.xsl +++ b/xsl/pretext-html.xsl @@ -12873,6 +12873,77 @@ along with PreTeXt. If not, see . + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ +

+ +
+

+ +

+
+ + + +
+

+
+
+ @@ -13098,6 +13169,37 @@ along with PreTeXt. If not, see . + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + +