diff --git a/static/css/s2.css b/static/css/s2.css index 2e79cf920d..88b2dc96c0 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -5937,32 +5937,31 @@ body .ui-autocomplete.dictionary-toc-autocomplete .ui-menu-item a.ui-state-focus font-style: normal; } + .rightButtons { display: flex; white-space: nowrap; flex-direction: row; text-align: left; } -.interface-english .leftButtons, -.interface-hebrew .rightButtons { +.rightButtons { display: flex; flex-direction: row; text-align: left; } -.interface-english .rightButtons, -.interface-hebrew .leftButtons { +.leftButtons { display: flex; flex-direction: row; text-align: left; } /* icons need a little nudge in flipped hebrew mode */ -.interface-hebrew .rightButtons { +.rightButtons { margin-left: -3px; } -.interface-hebrew .leftButtons { +.leftButtons { margin-right: 2px; } @@ -5987,7 +5986,7 @@ body .ui-autocomplete.dictionary-toc-autocomplete .ui-menu-item a.ui-state-focus left: -50px; } -.interface-hebrew .rightButtons .saveButton.tooltip-toggle::before { +.rightButtons .saveButton.tooltip-toggle::before { left: auto; right: -50px; } @@ -15606,6 +15605,201 @@ span.ref-link-color-3 { color: #666666; } +.dropdownMenu { + position: relative; + display: flex; + flex-direction: row-reverse; + z-index: 3; + } + + .dropdownButton { + border: none; + background-color: inherit; + } + + .rightButtons .dropdownButton { + text-align: end; + } + .dropdownLinks-menu { + display: contents; + } + + .dropdownLinks-menu.closed { + display: none; + } + + .texts-properties-menu { + width: 256px; + border: 1px solid var(--lighter-grey); + border-radius: 5px; + box-shadow: 0px 2px 4px var(--lighter-grey); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: absolute; + top: 100%; + background-color: white; + --english-font: var(--english-sans-serif-font-family); + --hebrew-font: var(--hebrew-sans-serif-font-family); + } + .texts-properties-menu, + .texts-properties-menu .button { + font-size: 14px; + } + + .show-source-translation-buttons { + display: flex; + flex-direction: column; + height: 140px; + justify-content: center; + } + + .show-source-translation-buttons .button { + margin: unset; + display: flex; + height: 35px; + width: 235px; + align-items: center; + justify-content: space-between; + box-shadow: unset; + padding-inline-start: 10px; + padding-inline-end: 0; + } + + .show-source-translation-buttons .button:has(input:not(:checked)) { + background-color: var(--lighter-grey); + color: black; + } + + .show-source-translation-buttons input[type=radio] { + appearance: none; + background-color: #fff; + width: 20px; + height: 20px; + border: 2px solid var(--medium-grey); + border-radius: 20px; + margin-top: 0; + margin-inline-end: 10px; + display: inline-grid; + place-content: center; + } + + .show-source-translation-buttons input[type=radio]:checked { + border: 0; + } + + .show-source-translation-buttons input[type=radio], + .show-source-translation-buttons label { + cursor: unset; + } + + .show-source-translation-buttons > div:first-of-type { + border-radius: 6px 6px 0 0; + } + + .show-source-translation-buttons > div:last-of-type { + border-radius: 0 0 6px 6px; + } + + .show-source-translation-buttons > div:not(:first-of-type):not(:last-of-type) { + border-radius: 0; + } + + .show-source-translation-buttons input[type=radio]:checked::before { + content: ""; + width: 10px; + height: 10px; + background-color: var(--sefaria-blue); + clip-path: polygon(13% 50%, 34% 66%, 81% 2%, 100% 18%, 39% 100%, 0 71%); + } + + .text-menu-border { + width: 100%; + height: 1px; + background-color: var(--lighter-grey); + + } + + .layout-button-line { + height: 57px; + width: 216px; + display: flex; + justify-content: space-between; + align-items: center; + } + + .layout-options { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + } + + .font-size-line { + width: 230px; + height: 50px; + display: flex; + justify-content: space-between; + align-items: center; + direction: ltr; + } + + .font-size-button { + display: flex; + align-items: center; + background-color: white; + border: none; + cursor: pointer; + } + + .layout-button-line { + height: 57px; + width: 216px; + display: flex; + justify-content: space-between; + align-items: center; + } + + .layout-options { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + } + + .layout-button input { + border: none; + width: 28px; + height: 24px; + -webkit-mask: var(--url) no-repeat; + -webkit-mask-size: contain; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: 100% 100%; + background-color: var(--medium-grey); + cursor: pointer; + appearance: unset; + transition: border 0.3s ease, outline 0.3s ease, background-color 0.3s ease; + } + + .layout-button input:checked { + background-color: var(--sefaria-blue); + } + + .layout-button .button { + background-color: unset; + box-shadow: unset; + padding: unset; + margin: unset; + } + + + + + + + @-webkit-keyframes load5 { 0%, 100% { box-shadow: 0 -2.6em 0 0 #ffffff, 1.8em -1.8em 0 0 rgba(0, 0, 0, 0.2), 2.5em 0 0 0 rgba(0, 0, 0, 0.2), 1.75em 1.75em 0 0 rgba(0, 0, 0, 0.2), 0 2.5em 0 0 rgba(0, 0, 0, 0.2), -1.8em 1.8em 0 0 rgba(0, 0, 0, 0.2), -2.6em 0 0 0 rgba(0, 0, 0, 0.5), -1.8em -1.8em 0 0 rgba(0, 0, 0, 0.7) diff --git a/static/icons/bi-ltr-heLeft.svg b/static/icons/bi-ltr-heLeft.svg new file mode 100644 index 0000000000..2d28ee586f --- /dev/null +++ b/static/icons/bi-ltr-heLeft.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/bi-ltr-stacked.svg b/static/icons/bi-ltr-stacked.svg new file mode 100644 index 0000000000..27fb13f535 --- /dev/null +++ b/static/icons/bi-ltr-stacked.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/bi-rtl-heRight.svg b/static/icons/bi-rtl-heRight.svg new file mode 100644 index 0000000000..67a7c095d0 --- /dev/null +++ b/static/icons/bi-rtl-heRight.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/bi-rtl-stacked.svg b/static/icons/bi-rtl-stacked.svg new file mode 100644 index 0000000000..035734e83f --- /dev/null +++ b/static/icons/bi-rtl-stacked.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/enlarge_font.svg b/static/icons/enlarge_font.svg new file mode 100644 index 0000000000..3c1ff999d6 --- /dev/null +++ b/static/icons/enlarge_font.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/mixed-beside-ltrrtl.svg b/static/icons/mixed-beside-ltrrtl.svg new file mode 100644 index 0000000000..f19de475c1 --- /dev/null +++ b/static/icons/mixed-beside-ltrrtl.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/mixed-beside-rtlltr.svg b/static/icons/mixed-beside-rtlltr.svg new file mode 100644 index 0000000000..a35ebe96f5 --- /dev/null +++ b/static/icons/mixed-beside-rtlltr.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/mixed-stacked-ltrrtl (1).svg b/static/icons/mixed-stacked-ltrrtl (1).svg new file mode 100644 index 0000000000..a0d8d6bb75 --- /dev/null +++ b/static/icons/mixed-stacked-ltrrtl (1).svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/mixed-stacked-ltrrtl.svg b/static/icons/mixed-stacked-ltrrtl.svg new file mode 100644 index 0000000000..a0d8d6bb75 --- /dev/null +++ b/static/icons/mixed-stacked-ltrrtl.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/mixed-stacked-rtlltr.svg b/static/icons/mixed-stacked-rtlltr.svg new file mode 100644 index 0000000000..3e088068f8 --- /dev/null +++ b/static/icons/mixed-stacked-rtlltr.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/mono-continuous.svg b/static/icons/mono-continuous.svg new file mode 100644 index 0000000000..f84c2f3fa8 --- /dev/null +++ b/static/icons/mono-continuous.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/mono-segmented.svg b/static/icons/mono-segmented.svg new file mode 100644 index 0000000000..9f1b6bffa9 --- /dev/null +++ b/static/icons/mono-segmented.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/icons/reduce_font.svg b/static/icons/reduce_font.svg new file mode 100644 index 0000000000..0714abe7fb --- /dev/null +++ b/static/icons/reduce_font.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/translation.svg b/static/img/translation.svg index d543bfec55..8d2ab66e60 100644 --- a/static/img/translation.svg +++ b/static/img/translation.svg @@ -1,4 +1,27 @@ - - - - + + + + diff --git a/static/js/LayoutButtons.jsx b/static/js/LayoutButtons.jsx new file mode 100644 index 0000000000..acd070c7e2 --- /dev/null +++ b/static/js/LayoutButtons.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import {useContext} from "react"; +import { ContentLanguageContext } from "./context"; +import {InterfaceText, RadioButton, layoutOptions, layoutLabels} from "./Misc"; +import PropTypes from "prop-types"; + +const calculateLayoutState = (language, textsData, panelMode) => { + const primaryDir = textsData?.primaryDirection; + const translationDir = textsData?.translationDirection; + return (language !== 'bilingual') ? 'mono' //one text + : (primaryDir == translationDir || panelMode === 'Sheet') ? 'mixed' //two texts with different directions + : (primaryDir === 'rtl') ? 'bi-rtl' //two rtl texts + : 'bi-ltr'; //two ltr texts +}; + +const getPath = (layoutOption, layoutState, textsData) => { + if (layoutState === 'mixed') { + const primaryDirection = textsData?.primaryDirection || 'rtl'; //no primary is the case of sheet + const translationDirection = textsData?.translationDirection || primaryDirection.split('').reverse().join(''); //when there is an empty translation it has no direction. we will show the button as opposite layouts. + const directions = (layoutOption === 'heLeft') ? `${primaryDirection}${translationDirection}` //heLeft means primary in left + : `${translationDirection}${primaryDirection}`; + if (layoutOption !== 'stacked') { + layoutOption = 'beside'; + } + layoutOption = `${layoutOption}-${directions}`; + } + return `/static/icons/${layoutState}-${layoutOption}.svg`; +}; + +const LayoutButton = ({layoutOption, layoutState}) => { + const {language, textsData, setOption, layout} = useContext(ContentLanguageContext); + const path = getPath(layoutOption, layoutState, textsData); + const optionName = (language === 'bilingual') ? 'biLayout' : 'layout'; + const checked = layout === layoutOption; + return ( +
+ setOption(optionName, layoutOption)} + name='layout-options' + isActive={checked} + value={layoutOption} + style={{"--url": `url(${path})`}} + /> +
+ ); +}; +LayoutButton.propTypes = { + layoutOption: PropTypes.string.isRequired, + layoutState: PropTypes.string.isRequired, +}; + +const LayoutButtons = () => { + const {language, textsData, panelMode} = useContext(ContentLanguageContext); + const layoutState = calculateLayoutState(language, textsData, panelMode); + return ( +
+ text.reader_option_menu.layout +
+ {layoutOptions[layoutState].map((option, index) => )} +
+
+ ); +}; + +export default LayoutButtons; \ No newline at end of file diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index ec2151113a..9a22c0218a 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1437,6 +1437,150 @@ DisplaySettingsButton.propTypes = { placeholder: PropTypes.bool, }; +const FontSizeButtons =() => { + const {setOption} = useContext(ContentLanguageContext); + return ( +
+ + text.reader_option_menu.font_size + +
+ ); +} +export default FontSizeButtons; + +const layoutOptions = { + 'mono': ['continuous', 'segmented'], + 'bi-rtl': ['stacked', 'heRight'], + 'bi-ltr': ['stacked', 'heLeft'], + 'mixed': ['stacked', 'heLeft', 'heRight'], +}; +const layoutLabels = { + 'continuous': 'Show Text as a paragram', + 'segmented': 'Show Text segmented', + 'stacked': 'Show Source & Translation Stacked', + 'heRight': 'Show RTL Text Right of LTR Text', + 'heLeft': 'Show RTL Text Left of LTR Text', +} + +const ToggleSwitchLine = ({name, onChange, isChecked, text, disabled=false}) => { + return ( +
+ {text} + +
+ ); +} +ToggleSwitchLine.propTypes = { + name: PropTypes.string, + disabled: PropTypes.bool, + text: PropTypes.string, + onChange: PropTypes.func.isRequired, + isChecked: PropTypes.bool.isRequired, +}; + +const RadioButton = ({isActive, onClick, value, name, label, ...rest}) => { + return ( +
+ + +
+ ); +} +RadioButton.propTypes = { + isActive: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, + value: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + label: PropTypes.string, +} +const DropdownMenu = ({children, buttonContent}) => { + /** + * buttonContent is a React component for the opening/closing of a button. + * the menu will be closed in click anywhere except in an element with classname 'preventClosing'. + * this class is using useRef for open/close rather than useState, for changing state triggers re-rendering of the + * component and all its children, so when clicking on children their onClick won't be executed. + */ + + const [isOpen, setIsOpen] = useState(false); + const wrapperRef = useRef(null); + + const handleButtonClick = (e) => { + e.stopPropagation(); + setIsOpen(isOpen => !isOpen); + }; + const handleContentsClick = (e) => { + e.stopPropagation(); + const preventClose = e.target.closest('.preventClosing'); + // Only toggle if no preventClose element was found + if (!preventClose) { + setIsOpen(false); + } + } + const handleHideDropdown = (event) => { + if (event.key === 'Escape') { + setIsOpen(false); + } + }; + const handleClickOutside = (event) => { + if ( + wrapperRef.current && + !wrapperRef.current.contains(event.target) + ) { + setIsOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('keydown', handleHideDropdown, true); + document.addEventListener('click', handleClickOutside, true); + return () => { + document.removeEventListener('keydown', handleHideDropdown, true); + document.removeEventListener('click', handleClickOutside, true); + }; + }, []); + +return ( +
+ +
+ {children} +
+
+); +}; +DropdownMenu.propTypes = { + buttonContent: PropTypes.elementType.isRequired, +}; function InterfaceLanguageMenu({currentLang, translationLanguagePreference, setTranslationLanguagePreference}){ const [isOpen, setIsOpen] = useState(false); @@ -3366,6 +3510,10 @@ export { ContentText, AppStoreButton, CategoryHeader, + DropdownMenu, + RadioButton, + FontSizeButtons, + ToggleSwitchLine, SimpleInterfaceBlock, DangerousInterfaceBlock, SimpleContentBlock, @@ -3432,5 +3580,7 @@ export { requestWithCallBack, OnInView, TopicPictureUploader, - ImageWithCaption + ImageWithCaption, + layoutOptions, + layoutLabels }; diff --git a/static/js/ReaderDisplayOptionsMenu.jsx b/static/js/ReaderDisplayOptionsMenu.jsx new file mode 100644 index 0000000000..ed593037c5 --- /dev/null +++ b/static/js/ReaderDisplayOptionsMenu.jsx @@ -0,0 +1,55 @@ +import React, {useContext} from "react"; +import Sefaria from "./sefaria/sefaria"; +import SourceTranslationsButtons from "./SourceTranslationsButtons"; +import {ContentLanguageContext} from "./context"; +import LayoutButtons from "./LayoutButtons"; +import {FontSizeButtons} from "./Misc"; + +const ReaderDisplayOptionsMenu = () => { + const {language, setOption, panelMode, textsData, width} = useContext(ContentLanguageContext); + + const isPanelModeSheet = panelMode === 'Sheet'; + const isSidePanel = !isPanelModeSheet && panelMode !== 'Text'; + const [HEBREW, ENGLISH, BILINGUAL] = ['hebrew', 'english', 'bilingual']; + + const showLangaugeToggle = () => { + if (Sefaria._siteSettings.TORAH_SPECIFIC) return true; + + const hasHebrew = !!textsData.he.length; + const hasEnglish = !!textsData.text.length; + return !(hasHebrew && hasEnglish); + }; + const showPrimary = language !== ENGLISH; + const showTranslation = language !== HEBREW; + const setShowTexts = (showPrimary, showTranslation) => { + const language = (showPrimary && showTranslation) ? BILINGUAL : (showPrimary) ? HEBREW : ENGLISH; + setOption('language', language); + }; + + const borderLine =
; + + const showLayoutsToggle = () => { + return !((width <= 600 && language === BILINGUAL) || + (isPanelModeSheet && language !== BILINGUAL)); + } + + return ( +
+ {showLangaugeToggle() && <> + + {borderLine} + } + {!isSidePanel && <> + {showLayoutsToggle() && } + {borderLine} + + } +
+ ); +}; +ReaderDisplayOptionsMenu.propTypes = {}; +export default ReaderDisplayOptionsMenu; \ No newline at end of file diff --git a/static/js/ReaderPanel.jsx b/static/js/ReaderPanel.jsx index 98bd120bd3..e91faa5af1 100644 --- a/static/js/ReaderPanel.jsx +++ b/static/js/ReaderPanel.jsx @@ -32,6 +32,7 @@ import ModeratorToolsPanel from './ModeratorToolsPanel'; import PublicCollectionsPage from './PublicCollectionsPage'; import TranslationsPage from './TranslationsPage'; import { TextColumnBannerChooser } from './TextColumnBanner'; +import ReaderDisplayOptionsMenu from './ReaderDisplayOptionsMenu' import { NavigateBackButton, CloseButton, @@ -42,6 +43,7 @@ import { CategoryAttribution, ToggleSet, InterfaceText, EnglishText, HebrewText, SignUpModal, MessageModel, + DropdownMenu } from './Misc'; import {ContentText} from "./ContentText"; @@ -666,6 +668,18 @@ class ReaderPanel extends Component { let items = []; let menu = null; let isNarrowColumn = false; + const readerPanelContextData = { + language: this.getContentLanguageOverride(this.state.settings.language, this.state.mode, this.state.menuOpen), + settings:this.state.settings, + multiPanel:this.props.multiPanel, + setOption:this.setOption, + panelMode:this.props.initialState.mode, + currentLayout:this.currentLayout, + currentBook:this.currentBook, + textsData:this.currentData(), + width:this.state.width, + menuOpen:this.state.menuOpen, + }; const contextContentLang = {"language": this.getContentLanguageOverride(this.state.settings.language, this.state.mode, this.state.menuOpen)}; (this.state.width < 730) ? isNarrowColumn = true : false; if (this.state.mode === "Text" || this.state.mode === "TextAndConnections") { @@ -1120,7 +1134,7 @@ class ReaderPanel extends Component { this.props.hideNavHeader ); return ( - +
{hideReaderControls ? null : : null} {this.state.displaySettingsOpen ? -
: null} +
: null} */}
@@ -1450,15 +1464,17 @@ class ReaderControls extends Component {
); + + const displaySettingsButton = (); + let displaySettingsMenu = (); let rightControls = hideHeader || connectionsHeader ? null : - (
- + {displaySettingsMenu}
); const openTransBannerApplies = () => Sefaria.openTransBannerApplies(this.props.currentBook(), this.props.settings.language); let banner = (hideHeader || connectionsHeader) ? null : ( @@ -1484,7 +1500,7 @@ class ReaderControls extends Component { ); return ( -
+
{connectionsHeader ? null : } {readerControls} {banner} @@ -1492,6 +1508,7 @@ class ReaderControls extends Component { ); } } + ReaderControls.propTypes = { settings: PropTypes.object.isRequired, showBaseText: PropTypes.func.isRequired, @@ -1499,13 +1516,11 @@ ReaderControls.propTypes = { setConnectionsMode: PropTypes.func.isRequired, setConnectionsCategory: PropTypes.func.isRequired, openMenu: PropTypes.func.isRequired, - openDisplaySettings: PropTypes.func.isRequired, openMobileNavMenu: PropTypes.func.isRequired, closeMenus: PropTypes.func.isRequired, currentMode: PropTypes.func.isRequired, currentCategory: PropTypes.func.isRequired, currentBook: PropTypes.func.isRequired, - currentLayout: PropTypes.func.isRequired, onError: PropTypes.func.isRequired, closePanel: PropTypes.func, toggleLanguage: PropTypes.func, @@ -1519,205 +1534,207 @@ ReaderControls.propTypes = { toggleSignUpModal: PropTypes.func.isRequired, historyObject: PropTypes.object, setTranslationLanguagePreference: PropTypes.func.isRequired, + backButtonSettings: PropTypes.object, }; -class ReaderDisplayOptionsMenu extends Component { - renderAliyotToggle() { - let torah = ["Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy", "Onkelos Genesis", "Onkelos Exodus", "Onkelos Leviticus", "Onkelos Numbers", "Onkelos Deuteronomy"]; - return this.props.currentBook ? torah.includes(this.props.currentBook()) : false; - } - vowelToggleAvailability(){ - let data = this.props.currentData(); - if(!data) return 2; - let sample = data["he"]; - while (Array.isArray(sample)) { - sample = sample[0]; - } - const vowels_re = /[\u05b0-\u05c3\u05c7]/g; - const cantillation_re = /[\u0591-\u05af]/g; - if(cantillation_re.test(sample)){ - //console.log("all"); - return 0; - }else if(vowels_re.test(sample)){ - //console.log("partial"); - return 1; - }else{ - //console.log("none"); - return 2; - } - } - showLangaugeToggle() { - if (Sefaria._siteSettings.TORAH_SPECIFIC) return true; - - let data = this.props.currentData(); - if (!data) return true // Sheets don't have currentData, also show for now (4x todo) - - const hasHebrew = !!data.he.length; - const hasEnglish = !!data.text.length; - return !(hasHebrew && hasEnglish); - } - shouldPunctuationToggleRender() { - if (this.props.currentData?.()?.primary_category === "Talmud" && (this.props.settings?.language === "hebrew" || this.props.settings?.language === "bilingual")) { return true; } - else { return false; } - } - - render() { - let languageOptions = [ - {name: "english", content: `A`, role: "radio", ariaLabel: "Show English Text" }, - {name: "bilingual", content: `A ${Sefaria.interfaceLang !== "english"? Sefaria._('text.reader_option_menu.font_size_lable'):'ཀ'}`, role: "radio", ariaLabel: "Show English & Hebrew Text" }, - {name: "hebrew", content: `${Sefaria.interfaceLang !== "english"? Sefaria._('text.reader_option_menu.font_size_lable'): 'ཀ'}`, role: "radio", ariaLabel: "Show Hebrew Text" } - ]; - let languageToggle = this.showLangaugeToggle() ? ( - ) : null; - - let layoutOptions = [ - {name: "continuous", fa: "align-justify", role: "radio", ariaLabel: "Show Text as a paragram" }, - {name: "segmented", fa: "align-left", role: "radio", ariaLabel: "Show Text segmented" }, - ]; - let biLayoutOptions = [ - {name: "stacked", content: "Stacked Language Toggle", role: "radio", ariaLabel: "Show Hebrew & English Stacked"}, - {name: "heLeft", content: "Hebrew Left Toggle", role: "radio", ariaLabel: "Show Hebrew Text Left of English Text"}, - {name: "heRight", content: "Hebrew Right Toggle", role: "radio", ariaLabel: "Show Hebrew Text Right of English Text"} - ]; - let layoutToggle = this.props.settings.language !== "bilingual" ? - this.props.parentPanel === "Sheet" ? null : - () : - (this.props.width > 500 ? - : null); - - let colorOptions = [ - {name: "light", content: "", role: "radio", ariaLabel: "Toggle light mode" }, - /*{name: "sepia", content: "", role: "radio", ariaLabel: "Toggle sepia mode" },*/ - {name: "dark", content: "", role: "radio", ariaLabel: "Toggle dark mode" } - ]; - let colorToggle = ( - ); - colorToggle = this.props.multiPanel ? null : colorToggle; - - let sizeOptions = [ - {name: "smaller", content: Sefaria._("text.reader_option_menu.font_size_lable"), role: "button", ariaLabel: Sefaria._("decrease_font_size") }, - {name: "larger", content: Sefaria._("text.reader_option_menu.font_size_lable"), role: "button", ariaLabel: Sefaria._("increase_font_size") } - ]; - let sizeToggle = ( - ); - - let aliyahOptions = [ - {name: "aliyotOn", content: Sefaria._("common.on"), role: "radio", ariaLabel: Sefaria._("Show Parasha Aliyot") }, - {name: "aliyotOff", content: Sefaria._("common.off"), role: "radio", ariaLabel: Sefaria._("Hide Parasha Aliyot") }, - ]; - let aliyahToggle = this.renderAliyotToggle() ? ( - this.props.parentPanel == "Sheet" ? null : - ) : null; - - let vowelsOptions = [ - {name: "all", content: "אָ֑", role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.show_vowels")}, - {name: "partial", content: "אָ", role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.show_only_vowels")}, - {name: "none", content: "א", role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.show_only_consonenetal_text")} - ]; - let vowelToggle = null; - if(!this.props.menuOpen){ - let vowelOptionsSlice = this.vowelToggleAvailability(); - let vowelOptionsTitle = (vowelOptionsSlice == 0) ? Sefaria._("text.reader_option_menu.vocalization") : Sefaria._("text.reader_option_menu.vowels"); - vowelsOptions = vowelsOptions.slice(vowelOptionsSlice); - vowelToggle = (this.props.settings.language !== "english" && vowelsOptions.length > 1) ? - this.props.parentPanel == "Sheet" ? null : - (): null; - } - let punctuationOptions = [ - {name: "punctuationOn", content: Sefaria._("common.on"), role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.show_puntuation")}, - {name: "punctuationOff", content: Sefaria._("common.off"), role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.hide_puntuation")} - ] - let punctuationToggle = this.shouldPunctuationToggleRender() ? ( - ) : null; - if (this.props.menuOpen === "search") { - return (
-
- {languageToggle} - {sizeToggle} -
-
); - } else if (this.props.menuOpen) { - return (
-
- {languageToggle} -
-
); - } else { - return (
-
- {languageToggle} - {layoutToggle} - {colorToggle} - {sizeToggle} - {aliyahToggle} - {vowelToggle} - {punctuationToggle} -
-
); - } - } -} -ReaderDisplayOptionsMenu.propTypes = { - setOption: PropTypes.func.isRequired, - currentLayout: PropTypes.func.isRequired, - currentBook: PropTypes.func, - currentData: PropTypes.func, - menuOpen: PropTypes.string, - multiPanel: PropTypes.bool.isRequired, - width: PropTypes.number.isRequired, - settings: PropTypes.object.isRequired, -}; +// class ReaderDisplayOptionsMenu extends Component { +// renderAliyotToggle() { +// let torah = ["Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy", "Onkelos Genesis", "Onkelos Exodus", "Onkelos Leviticus", "Onkelos Numbers", "Onkelos Deuteronomy"]; +// return this.props.currentBook ? torah.includes(this.props.currentBook()) : false; +// } +// vowelToggleAvailability(){ +// let data = this.props.currentData(); +// if(!data) return 2; +// let sample = data["he"]; +// while (Array.isArray(sample)) { +// sample = sample[0]; +// } +// const vowels_re = /[\u05b0-\u05c3\u05c7]/g; +// const cantillation_re = /[\u0591-\u05af]/g; +// if(cantillation_re.test(sample)){ +// //console.log("all"); +// return 0; +// }else if(vowels_re.test(sample)){ +// //console.log("partial"); +// return 1; +// }else{ +// //console.log("none"); +// return 2; +// } +// } +// showLangaugeToggle() { +// if (Sefaria._siteSettings.TORAH_SPECIFIC) return true; + +// let data = this.props.currentData(); +// if (!data) return true // Sheets don't have currentData, also show for now (4x todo) + +// const hasHebrew = !!data.he.length; +// const hasEnglish = !!data.text.length; +// return !(hasHebrew && hasEnglish); +// } +// shouldPunctuationToggleRender() { +// if (this.props.currentData?.()?.primary_category === "Talmud" && (this.props.settings?.language === "hebrew" || this.props.settings?.language === "bilingual")) { return true; } +// else { return false; } +// } + +// render() { +// let languageOptions = [ +// {name: "english", content: `A`, role: "radio", ariaLabel: "Show English Text" }, +// {name: "bilingual", content: `A ${Sefaria.interfaceLang !== "english"? Sefaria._('text.reader_option_menu.font_size_lable'):'ཀ'}`, role: "radio", ariaLabel: "Show English & Hebrew Text" }, +// {name: "hebrew", content: `${Sefaria.interfaceLang !== "english"? Sefaria._('text.reader_option_menu.font_size_lable'): 'ཀ'}`, role: "radio", ariaLabel: "Show Hebrew Text" } +// ]; +// let languageToggle = this.showLangaugeToggle() ? ( +// ) : null; + +// let layoutOptions = [ +// {name: "continuous", fa: "align-justify", role: "radio", ariaLabel: "Show Text as a paragram" }, +// {name: "segmented", fa: "align-left", role: "radio", ariaLabel: "Show Text segmented" }, +// ]; +// let biLayoutOptions = [ +// {name: "stacked", content: "Stacked Language Toggle", role: "radio", ariaLabel: "Show Hebrew & English Stacked"}, +// {name: "heLeft", content: "Hebrew Left Toggle", role: "radio", ariaLabel: "Show Hebrew Text Left of English Text"}, +// {name: "heRight", content: "Hebrew Right Toggle", role: "radio", ariaLabel: "Show Hebrew Text Right of English Text"} +// ]; +// let layoutToggle = this.props.settings.language !== "bilingual" ? +// this.props.parentPanel === "Sheet" ? null : +// () : +// (this.props.width > 500 ? +// : null); + +// let colorOptions = [ +// {name: "light", content: "", role: "radio", ariaLabel: "Toggle light mode" }, +// /*{name: "sepia", content: "", role: "radio", ariaLabel: "Toggle sepia mode" },*/ +// {name: "dark", content: "", role: "radio", ariaLabel: "Toggle dark mode" } +// ]; +// let colorToggle = ( +// ); +// colorToggle = this.props.multiPanel ? null : colorToggle; + +// let sizeOptions = [ +// {name: "smaller", content: Sefaria._("text.reader_option_menu.font_size_lable"), role: "button", ariaLabel: Sefaria._("decrease_font_size") }, +// {name: "larger", content: Sefaria._("text.reader_option_menu.font_size_lable"), role: "button", ariaLabel: Sefaria._("increase_font_size") } +// ]; +// let sizeToggle = ( +// ); + +// let aliyahOptions = [ +// {name: "aliyotOn", content: Sefaria._("common.on"), role: "radio", ariaLabel: Sefaria._("Show Parasha Aliyot") }, +// {name: "aliyotOff", content: Sefaria._("common.off"), role: "radio", ariaLabel: Sefaria._("Hide Parasha Aliyot") }, +// ]; +// let aliyahToggle = this.renderAliyotToggle() ? ( +// this.props.parentPanel == "Sheet" ? null : +// ) : null; + +// let vowelsOptions = [ +// {name: "all", content: "אָ֑", role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.show_vowels")}, +// {name: "partial", content: "אָ", role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.show_only_vowels")}, +// {name: "none", content: "א", role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.show_only_consonenetal_text")} +// ]; +// let vowelToggle = null; +// if(!this.props.menuOpen){ +// let vowelOptionsSlice = this.vowelToggleAvailability(); +// let vowelOptionsTitle = (vowelOptionsSlice == 0) ? Sefaria._("text.reader_option_menu.vocalization") : Sefaria._("text.reader_option_menu.vowels"); +// vowelsOptions = vowelsOptions.slice(vowelOptionsSlice); +// vowelToggle = (this.props.settings.language !== "english" && vowelsOptions.length > 1) ? +// this.props.parentPanel == "Sheet" ? null : +// (): null; +// } + +// let punctuationOptions = [ +// {name: "punctuationOn", content: Sefaria._("common.on"), role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.show_puntuation")}, +// {name: "punctuationOff", content: Sefaria._("common.off"), role: "radio", ariaLabel: Sefaria._("text.reader_option_menu.hide_puntuation")} +// ] +// let punctuationToggle = this.shouldPunctuationToggleRender() ? ( +// ) : null; +// if (this.props.menuOpen === "search") { +// return (
+//
+// {languageToggle} +// {sizeToggle} +//
+//
); +// } else if (this.props.menuOpen) { +// return (
+//
+// {languageToggle} +//
+//
); +// } else { +// return (
+//
+// {languageToggle} +// {layoutToggle} +// {colorToggle} +// {sizeToggle} +// {aliyahToggle} +// {vowelToggle} +// {punctuationToggle} +//
+//
); +// } +// } +// } +// ReaderDisplayOptionsMenu.propTypes = { +// setOption: PropTypes.func.isRequired, +// currentLayout: PropTypes.func.isRequired, +// currentBook: PropTypes.func, +// currentData: PropTypes.func, +// menuOpen: PropTypes.string, +// multiPanel: PropTypes.bool.isRequired, +// width: PropTypes.number.isRequired, +// settings: PropTypes.object.isRequired, +// }; export default ReaderPanel; diff --git a/static/js/SourceTranslationsButtons.jsx b/static/js/SourceTranslationsButtons.jsx new file mode 100644 index 0000000000..1ef2526e38 --- /dev/null +++ b/static/js/SourceTranslationsButtons.jsx @@ -0,0 +1,34 @@ +import React, {useContext} from "react"; +import PropTypes from "prop-types"; +import {ContentLanguageContext} from "./context"; +import {RadioButton} from "./Misc"; +import Sefaria from './sefaria/sefaria'; + +function SourceTranslationsButtons({ showPrimary, showTranslation, setShowTexts }) { + const {panelMode} = useContext(ContentLanguageContext); + const isSidePanel = !['Text', 'Sheet'].includes(panelMode); + const createButton = (isPrimary, isTranslation, text) => { + const isActive = (isPrimary === showPrimary && isTranslation === showTranslation); + return ( setShowTexts(isPrimary, isTranslation)} + value={text} + name='languageOptions' + label={text} + />); + }; + + return ( +
+ {createButton(true, false, Sefaria._('text.reader_option_menu.source'))} + {createButton(false, true, Sefaria._('text.reader_option_menu.translation'))} + {!isSidePanel && createButton(true, true, Sefaria._('text.reader_option_menu.source_with_translation'))} +
+ ); +} +SourceTranslationsButtons.propTypes = { + showPrimary: PropTypes.bool.isRequired, + showTranslation: PropTypes.bool.isRequired, + setShowTexts: PropTypes.func.isRequired, +} +export default SourceTranslationsButtons \ No newline at end of file diff --git a/static/js/context.js b/static/js/context.js index 8c9f1f03f4..70d416d34a 100644 --- a/static/js/context.js +++ b/static/js/context.js @@ -163,6 +163,14 @@ function StrapiDataProvider({ children }) { showTo startTime updatedAt + isNewsletterSubscriptionInputForm + newsletterMailingLists { + data { + attributes { + newsletterName + } + } + } } } } diff --git a/static/js/sefaria/localizationLanguage/chinese.json b/static/js/sefaria/localizationLanguage/chinese.json index 8a6bcc2a23..89c911a920 100644 --- a/static/js/sefaria/localizationLanguage/chinese.json +++ b/static/js/sefaria/localizationLanguage/chinese.json @@ -149,6 +149,9 @@ "sheet.create_new": "创建新项目", "sheet.publish_setting": "发布设置", "sheet.published": "已发布", + "text.reader_option_menu.source": "来源", + "text.reader_option_menu.translation": "翻译", + "text.reader_option_menu.source_with_translation": "原文与翻译", "text.reader_option_menu.color": "颜色", "text.reader_option_menu.font_size": "字体大小", "text.reader_option_menu.punctuation": "标点", diff --git a/static/js/sefaria/localizationLanguage/english.json b/static/js/sefaria/localizationLanguage/english.json index 98f0baf9e2..f56ba60065 100644 --- a/static/js/sefaria/localizationLanguage/english.json +++ b/static/js/sefaria/localizationLanguage/english.json @@ -150,6 +150,9 @@ "sheet.publish_setting": "Publish Settings", "sheet.published":"Published", "text.reader_option_menu.color":"Color", + "text.reader_option_menu.source": "Source", + "text.reader_option_menu.translation": "Translation", + "text.reader_option_menu.source_with_translation": "Source with Translation", "text.reader_option_menu.font_size":"Font Size", "text.reader_option_menu.punctuation":"Punctuation", "text.reader_option_menu.show_puntuation":"Show Punctuation", diff --git a/static/js/sefaria/localizationLanguage/tibetan.json b/static/js/sefaria/localizationLanguage/tibetan.json index 5674b1b9a8..4d45d031da 100644 --- a/static/js/sefaria/localizationLanguage/tibetan.json +++ b/static/js/sefaria/localizationLanguage/tibetan.json @@ -149,6 +149,9 @@ "sheet.create_new": "གསར་པ་བཟོ།", "sheet.publish_setting": "སྤེལ་གྱི་སྒྲིག་འགོད།", "sheet.published": "འབྲི་བཞག", + "text.reader_option_menu.source": "ཁུངས།", + "text.reader_option_menu.translation": "འགྱུར་མ།", + "text.reader_option_menu.source_with_translation": "ཁུངས་དང་འགྱུར་མ།", "text.reader_option_menu.color":"ཚོན་མདོག", "text.reader_option_menu.font_size":"ཡིག་གཟུགས་ཆེ་ཆུང་།", "text.reader_option_menu.punctuation":"ཚེག་ཤད།",