From fbba29d8298d912d4a27395f30d273c0539fb530 Mon Sep 17 00:00:00 2001 From: yishai Date: Tue, 13 Jun 2023 12:50:15 +0300 Subject: [PATCH 001/260] feat((TextChunck): add new class TextRange and moving most of TextChunck methods to it. --- sefaria/model/text.py | 438 ++++++++++++++++++++++++------------------ 1 file changed, 248 insertions(+), 190 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index b6ab4c2765..87aa556be9 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1686,28 +1686,12 @@ def __call__(cls, *args, **kwargs): else: return super(TextFamilyDelegator, cls).__call__(*args, **kwargs) - -class TextChunk(AbstractTextRecord, metaclass=TextFamilyDelegator): - """ - A chunk of text corresponding to the provided :class:`Ref`, language, and optional version name. - If it is possible to get a more complete text by merging multiple versions, a merged result will be returned. - - :param oref: :class:`Ref` - :param lang: "he" or "en". "he" means all rtl languages and "en" means all ltr languages - :param vtitle: optional. Title of the version desired. - :param actual_lang: optional. if vtitle isn't specified, prefer to find a version with ISO language `actual_lang`. As opposed to `lang` which can only be "he" or "en", `actual_lang` can be any valid 2 letter ISO language code. - """ +class TextRange(AbstractTextRecord, metaclass=TextFamilyDelegator): text_attr = "text" - def __init__(self, oref, lang="en", vtitle=None, exclude_copyrighted=False, actual_lang=None, fallback_on_default_version=False): - """ - :param oref: - :type oref: Ref - :param lang: "he" or "en" - :param vtitle: - :return: - """ + def __init__(self, oref, vtitle=None, actual_lang=None, **kwargs): + #kwargs are only for supporting old TextChunck. should be removed after merging if isinstance(oref.index_node, JaggedArrayNode): self._oref = oref else: @@ -1716,54 +1700,41 @@ def __init__(self, oref, lang="en", vtitle=None, exclude_copyrighted=False, actu raise InputError("Can not get TextChunk at this level, please provide a more precise reference") self._oref = child_ref self._ref_depth = len(self._oref.sections) + self.actual_lang = actual_lang self._versions = [] self._version_ids = None - self._saveable = False # Can this TextChunk be saved? - - self.lang = lang + self._saveable = False self.is_merged = False self.sources = [] self.text = self._original_text = self.empty_text() self.vtitle = vtitle - self.full_version = None self.versionSource = None # handling of source is hacky + self._choose_version(actual_lang=actual_lang, **kwargs) #kactual_lang and wargs are only for supporting old TextChunck. should be removed after merging - if lang and vtitle and not fallback_on_default_version: + def _choose_version(self, **kwargs): #kwargs are only for supporting old TextChunck. should be removed after merging + if self.actual_lang and self.vtitle: self._saveable = True - v = Version().load({"title": self._oref.index.title, "language": lang, "versionTitle": vtitle}, self._oref.part_projection()) - if exclude_copyrighted and v.is_copyrighted(): - raise InputError("Can not provision copyrighted text. {} ({}/{})".format(oref.normal(), vtitle, lang)) + v = Version().load({"title": self._oref.index.title, "actualLanguage": self.actual_lang, "versionTitle": self.vtitle}, self._oref.part_projection()) if v: self._versions += [v] self.text = self._original_text = self.trim_text(v.content_node(self._oref.index_node)) - elif lang: - if actual_lang is not None: - self._choose_version_by_lang(oref, lang, exclude_copyrighted, actual_lang, prioritized_vtitle=vtitle) - else: - self._choose_version_by_lang(oref, lang, exclude_copyrighted, prioritized_vtitle=vtitle) + elif self.actual_lang: + self._choose_version_by_lang() else: raise Exception("TextChunk requires a language.") + if not self._versions: + version_title_message = f" and version title '{self.vtile}'" if self.vtile else '' + raise NoVersionFoundError(f"No text record found for '{self._oref.index.title}' in {self.actual_lang}{version_title_message}") - def _choose_version_by_lang(self, oref, lang: str, exclude_copyrighted: bool, actual_lang: str = None, prioritized_vtitle: str = None) -> None: - if prioritized_vtitle: - actual_lang = None - vset = VersionSet(self._oref.condition_query(lang, actual_lang), proj=self._oref.part_projection()) - if len(vset) == 0: - if VersionSet({"title": self._oref.index.title}).count() == 0: - raise NoVersionFoundError("No text record found for '{}'".format(self._oref.index.title)) - return + def _choose_version_by_lang(self) -> None: + vset = VersionSet(self._oref.condition_query(actual_lang=self.actual_lang), proj=self._oref.part_projection()) if len(vset) == 1: v = vset[0] - if exclude_copyrighted and v.is_copyrighted(): - raise InputError("Can not provision copyrighted text. {} ({}/{})".format(oref.normal(), v.versionTitle, v.language)) self._versions += [v] self.text = self.trim_text(v.content_node(self._oref.index_node)) - #todo: Should this instance, and the non-merge below, be made saveable? - else: # multiple versions available, merge - if exclude_copyrighted: - vset.remove(Version.is_copyrighted) - merged_text, sources = vset.merge(self._oref.index_node, prioritized_vtitle=prioritized_vtitle) #todo: For commentaries, this merges the whole chapter. It may show up as merged, even if our part is not merged. + elif len(vset) > 1: # multiple versions available, merge + merged_text, sources = vset.merge(self._oref.index_node) #todo: For commentaries, this merges the whole chapter. It may show up as merged, even if our part is not merged. self.text = self.trim_text(merged_text) if len(set(sources)) == 1: for v in vset: @@ -1776,16 +1747,13 @@ def _choose_version_by_lang(self, oref, lang: str, exclude_copyrighted: bool, ac self._versions = vset.array() def __str__(self): - args = "{}, {}".format(self._oref, self.lang) + args = f"{self._oref}, {self.actual_lang}" if self.vtitle: - args += ", {}".format(self.vtitle) + args += f", {self.vtitle}" return args def __repr__(self): # Wanted to use orig_tref, but repr can not include Unicode - args = "{}, {}".format(self._oref, self.lang) - if self.vtitle: - args += ", {}".format(self.vtitle) - return "{}({})".format(self.__class__.__name__, args) + return f"{self.__class__.__name__}({self.__str__()})" def version_ids(self): if self._version_ids is None: @@ -1806,55 +1774,6 @@ def ja(self, remove_html=False): else: return JaggedTextArray(self.text) - def save(self, force_save=False): - """ - For editing in place (i.e. self.text[3] = "Some text"), it is necessary to set force_save to True. This is - because by editing in place, both the self.text and the self._original_text fields will get changed, - causing the save to abort. - :param force_save: If set to True, will force a save even if no change was detected in the text. - :return: - """ - assert self._saveable, "Tried to save a read-only text: {}".format(self._oref.normal()) - assert not self._oref.is_range(), "Only non-range references can be saved: {}".format(self._oref.normal()) - #may support simple ranges in the future. - #self._oref.is_range() and self._oref.range_index() == len(self._oref.sections) - 1 - if not force_save: - if self.text == self._original_text: - logger.warning("Aborted save of {}. No change in text.".format(self._oref.normal())) - return False - - self._validate() - self._sanitize() - self._trim_ending_whitespace() - - if not self.version(): - self.full_version = Version( - { - "chapter": self._oref.index.nodes.create_skeleton(), - "versionTitle": self.vtitle, - "versionSource": self.versionSource, - "language": self.lang, - "title": self._oref.index.title - } - ) - else: - self.full_version = Version().load({"title": self._oref.index.title, "language": self.lang, "versionTitle": self.vtitle}) - assert self.full_version, "Failed to load Version record for {}, {}".format(self._oref.normal(), self.vtitle) - if self.versionSource: - self.full_version.versionSource = self.versionSource # hack - - content = self.full_version.sub_content(self._oref.index_node.version_address()) - self._pad(content) - self.full_version.sub_content(self._oref.index_node.version_address(), [i - 1 for i in self._oref.sections], self.text) - - self._check_available_text_pre_save() - - self.full_version.save() - self._oref.recalibrate_next_prev_refs(len(self.text)) - self._update_link_language_availability() - - return self - def _pad(self, content): """ Pads the passed content to the dimension of self._oref. @@ -1880,61 +1799,6 @@ def _pad(self, content): if pos < self._ref_depth - 2 and isinstance(parent_content[val - 1], str): parent_content[val - 1] = [parent_content[val - 1]] - def _check_available_text_pre_save(self): - """ - Stores the availability of this text in before a save is made, - so that we can know if segments have been added or deleted overall. - """ - self._available_text_pre_save = {} - langs_checked = [self.lang] # swtich to ["en", "he"] when global availability checks are needed - for lang in langs_checked: - try: - self._available_text_pre_save[lang] = self._oref.text(lang=lang).text - except NoVersionFoundError: - self._available_text_pre_save[lang] = [] - - def _check_available_segments_changed_post_save(self, lang=None): - """ - Returns a list of tuples containing a Ref and a boolean availability - for each Ref that was either made available or unavailble for `lang`. - If `lang` is None, returns changed availability across all langauges. - """ - if lang: - old_refs_available = self._text_to_ref_available(self._available_text_pre_save[self.lang]) - else: - # Looking for availability of in all langauges, merge results of Hebrew and English - old_en_refs_available = self._text_to_ref_available(self._available_text_pre_save["en"]) - old_he_refs_available = self._text_to_ref_available(self._available_text_pre_save["he"]) - zipped = list(itertools.zip_longest(old_en_refs_available, old_he_refs_available)) - old_refs_available = [] - for item in zipped: - en, he = item[0], item[1] - ref = en[0] if en else he[0] - old_refs_available.append((ref, (en and en[1] or he and he[1]))) - - new_refs_available = self._text_to_ref_available(self.text) - - changed = [] - zipped = list(itertools.zip_longest(old_refs_available, new_refs_available)) - for item in zipped: - old_text, new_text = item[0], item[1] - had_previously = old_text and old_text[1] - have_now = new_text and new_text[1] - - if not had_previously and have_now: - changed.append(new_text) - elif had_previously and not have_now: - # Current save is deleting a line of text, but it could still be - # available in a different version for this language. Check again. - if lang: - text_still_available = bool(old_text[0].text(lang=lang).text) - else: - text_still_available = bool(old_text[0].text("en").text) or bool(old_text[0].text("he").text) - if not text_still_available: - changed.append([old_text[0], False]) - - return changed - def _text_to_ref_available(self, text): """Converts a JaggedArray of text to flat list of (Ref, bool) if text is availble""" flat = JaggedArray(text).flatten_to_array_with_indices() @@ -1948,18 +1812,6 @@ def _text_to_ref_available(self, text): refs_available += [[ref, available]] return refs_available - def _update_link_language_availability(self): - """ - Check if current save has changed the overall availabilty of text for refs - in this language, pass refs to update revelant links if so. - """ - changed = self._check_available_segments_changed_post_save(lang=self.lang) - - if len(changed): - from . import link - for change in changed: - link.update_link_language_availabiliy(change[0], self.lang, change[1]) - def _validate(self): """ validate that depth/breadth of the TextChunk.text matches depth/breadth of the Ref @@ -1970,8 +1822,8 @@ def _validate(self): implied_depth = ref_depth + posted_depth if implied_depth != self._oref.index_node.depth: raise InputError( - "Text Structure Mismatch. The stored depth of {} is {}, but the text posted to {} implies a depth of {}." - .format(self._oref.index_node.full_title(), self._oref.index_node.depth, self._oref.normal(), implied_depth) + f"Text Structure Mismatch. The stored depth of {self._oref.index_node.full_title()} is {self._oref.index_node.depth}, \ + but the text posted to {self._oref.normal()} implies a depth of {implied_depth}." ) #validate that length of the array matches length of the ref @@ -1980,20 +1832,18 @@ def _validate(self): span_size = self._oref.span_size() if posted_depth == 0: #possible? raise InputError( - "Text Structure Mismatch. {} implies a length of {} sections, but the text posted is a string." - .format(self._oref.normal(), span_size) + f"Text Structure Mismatch. {self._oref.normal()} implies a length of {span_size} sections, but the text posted is a string." ) elif posted_depth == 1: #possible? raise InputError( - "Text Structure Mismatch. {} implies a length of {} sections, but the text posted is a simple list." - .format(self._oref.normal(), span_size) + f"Text Structure Mismatch. {self._oref.normal()} implies a length of {span_size} sections, but the text posted is a simple list." ) else: posted_length = len(self.text) if posted_length != span_size: raise InputError( - "Text Structure Mismatch. {} implies a length of {} sections, but the text posted has {} elements." - .format(self._oref.normal(), span_size, posted_length) + f"Text Structure Mismatch. {self._oref.normal()} implies a length of {span_size} sections, \ + but the text posted has {posted_length} elements." ) #todo: validate last section size if provided @@ -2001,23 +1851,21 @@ def _validate(self): range_length = self._oref.range_size() if posted_depth == 0: raise InputError( - "Text Structure Mismatch. {} implies a length of {}, but the text posted is a string." - .format(self._oref.normal(), range_length) + f"Text Structure Mismatch. {self._oref.normal()} implies a length of {range_length}, but the text posted is a string." ) elif posted_depth == 1: posted_length = len(self.text) if posted_length != range_length: raise InputError( - "Text Structure Mismatch. {} implies a length of {}, but the text posted has {} elements." - .format(self._oref.normal(), range_length, posted_length) + f"Text Structure Mismatch. {self._oref.normal()} implies a length of {range_length}, \ + but the text posted has {posted_length} elements." ) else: # this should never happen. The depth check should catch it. raise InputError( - "Text Structure Mismatch. {} implies an simple array of length {}, but the text posted has depth {}." - .format(self._oref.normal(), range_length, posted_depth) + f"Text Structure Mismatch. {self._oref.normal()} implies an simple array of length {range_length}, \ + but the text posted has depth {posted_depth}." ) - #maybe use JaggedArray.subarray()? def trim_text(self, txt): """ Trims a text loaded from Version record with self._oref.part_projection() to the specifications of self._oref @@ -2086,9 +1934,13 @@ def has_manually_wrapped_refs(self): # merged version return False + def get_language(self): + #method for getting a different lang attribue in old TextChunck and RangeText + #can be removed after merging and all uses can be replaced by self.acutal_lang + return self.actual_lang + def nonempty_segment_refs(self): """ - :return: list of segment refs with content in this TextChunk """ r = self._oref @@ -2100,7 +1952,7 @@ def nonempty_segment_refs(self): else: input_refs = [r] for temp_ref in input_refs: - temp_tc = temp_ref.text(lang=self.lang, vtitle=self.vtitle) + temp_tc = temp_ref.text(lang=self.get_language(), vtitle=self.vtitle) ja = temp_tc.ja() jarray = ja.mask().array() @@ -2129,9 +1981,9 @@ def find_string(self, regex_str, cleaner=lambda x: x, strict=True): text_list = [x for x in self.ja().flatten_to_array() if len(x) > 0] if len(text_list) != len(ref_list): if strict: - raise ValueError("The number of refs doesn't match the number of starting words. len(refs)={} len(inds)={}".format(len(ref_list),len(ind_list))) + raise ValueError(f"The number of refs doesn't match the number of starting words. len(refs)={len(ref_list)} len(inds)={len(text_list)}") else: - print("Warning: The number of refs doesn't match the number of starting words. len(refs)={} len(inds)={} {}".format(len(ref_list),len(ind_list),str(self._oref))) + print(f"Warning: The number of refs doesn't match the number of starting words. len(refs)={len(ref_list)} len(inds)={len(text_list)}") matches = [] for r, t in zip(ref_list, text_list): @@ -2164,9 +2016,9 @@ def text_index_map(self, tokenizer=lambda x: re.split(r'\s+', x), strict=True, r if len(ind_list) != len(ref_list): if strict: - raise ValueError("The number of refs doesn't match the number of starting words. len(refs)={} len(inds)={}".format(len(ref_list),len(ind_list))) + raise ValueError(f"The number of refs doesn't match the number of starting words. len(refs)={len(ref_list)} len(inds)={len(ind_list)}") else: - print("Warning: The number of refs doesn't match the number of starting words. len(refs)={} len(inds)={} {}".format(len(ref_list),len(ind_list),str(self._oref))) + print(f"Warning: The number of refs doesn't match the number of starting words. len(refs)={len(ref_list)} len(inds)={len(ind_list)}") if len(ind_list) > len(ref_list): ind_list = ind_list[:len(ref_list)] else: @@ -2178,6 +2030,212 @@ def text_index_map(self, tokenizer=lambda x: re.split(r'\s+', x), strict=True, r return ind_list, ref_list, total_len +class TextChunk(TextRange, metaclass=TextFamilyDelegator): + """ + A chunk of text corresponding to the provided :class:`Ref`, language, and optional version name. + If it is possible to get a more complete text by merging multiple versions, a merged result will be returned. + + :param oref: :class:`Ref` + :param lang: "he" or "en". "he" means all rtl languages and "en" means all ltr languages + :param vtitle: optional. Title of the version desired. + :param actual_lang: optional. if vtitle isn't specified, prefer to find a version with ISO language `actual_lang`. As opposed to `lang` which can only be "he" or "en", `actual_lang` can be any valid 2 letter ISO language code. + """ + + text_attr = "text" + + def __init__(self, oref, lang="en", vtitle=None, exclude_copyrighted=False, actual_lang=None, fallback_on_default_version=False): + """ + :param oref: + :type oref: Ref + :param lang: "he" or "en" + :param vtitle: + :return: + """ + + self.lang = lang + super(TextChunk, self).__init__(oref=oref, vtitle=vtitle, exclude_copyrighted=exclude_copyrighted, actual_lang=actual_lang, fallback_on_default_version=fallback_on_default_version) + + def _choose_version(self, exclude_copyrighted, actual_lang, fallback_on_default_version): + if self.lang and self.vtitle and not fallback_on_default_version: + self._saveable = True + v = Version().load({"title": self._oref.index.title, "language": self.lang, "versionTitle": self.vtitle}, self._oref.part_projection()) + if exclude_copyrighted and v.is_copyrighted(): + raise InputError(f"Can not provision copyrighted text. {self._oref.normal()} ({self.vtitle}/{self.lang})") + if v: + self._versions += [v] + self.text = self._original_text = self.trim_text(v.content_node(self._oref.index_node)) + elif self.lang: + if actual_lang is not None: + self._choose_version_by_lang(self.lang, exclude_copyrighted, actual_lang, prioritized_vtitle=self.vtitle) + else: + self._choose_version_by_lang(self.lang, exclude_copyrighted, prioritized_vtitle=self.vtitle) + else: + raise Exception("TextChunk requires a language.") + + def _choose_version_by_lang(self, lang: str, exclude_copyrighted: bool, actual_lang: str = None, prioritized_vtitle: str = None) -> None: + if prioritized_vtitle: + actual_lang = None + vset = VersionSet(self._oref.condition_query(lang, actual_lang), proj=self._oref.part_projection()) + if len(vset) == 0: + if VersionSet({"title": self._oref.index.title}).count() == 0: + raise NoVersionFoundError("No text record found for '{}'".format(self._oref.index.title)) + return + if len(vset) == 1: + v = vset[0] + if exclude_copyrighted and v.is_copyrighted(): + raise InputError("Can not provision copyrighted text. {} ({}/{})".format(oref.normal(), v.versionTitle, v.language)) + self._versions += [v] + self.text = self.trim_text(v.content_node(self._oref.index_node)) + #todo: Should this instance, and the non-merge below, be made saveable? + else: # multiple versions available, merge + if exclude_copyrighted: + vset.remove(Version.is_copyrighted) + merged_text, sources = vset.merge(self._oref.index_node, prioritized_vtitle=prioritized_vtitle) #todo: For commentaries, this merges the whole chapter. It may show up as merged, even if our part is not merged. + self.text = self.trim_text(merged_text) + if len(set(sources)) == 1: + for v in vset: + if v.versionTitle == sources[0]: + self._versions += [v] + break + else: + self.sources = sources + self.is_merged = True + self._versions = vset.array() + + def __str__(self): + args = "{}, {}".format(self._oref, self.lang) + if self.vtitle: + args += ", {}".format(self.vtitle) + return args + + def __repr__(self): # Wanted to use orig_tref, but repr can not include Unicode + args = "{}, {}".format(self._oref, self.lang) + if self.vtitle: + args += ", {}".format(self.vtitle) + return "{}({})".format(self.__class__.__name__, args) + + def save(self, force_save=False): + """ + For editing in place (i.e. self.text[3] = "Some text"), it is necessary to set force_save to True. This is + because by editing in place, both the self.text and the self._original_text fields will get changed, + causing the save to abort. + :param force_save: If set to True, will force a save even if no change was detected in the text. + :return: + """ + assert self._saveable, "Tried to save a read-only text: {}".format(self._oref.normal()) + assert not self._oref.is_range(), "Only non-range references can be saved: {}".format(self._oref.normal()) + #may support simple ranges in the future. + #self._oref.is_range() and self._oref.range_index() == len(self._oref.sections) - 1 + if not force_save: + if self.text == self._original_text: + logger.warning("Aborted save of {}. No change in text.".format(self._oref.normal())) + return False + + self._validate() + self._sanitize() + self._trim_ending_whitespace() + + if not self.version(): + self.full_version = Version( + { + "chapter": self._oref.index.nodes.create_skeleton(), + "versionTitle": self.vtitle, + "versionSource": self.versionSource, + "language": self.lang, + "title": self._oref.index.title + } + ) + else: + self.full_version = Version().load({"title": self._oref.index.title, "language": self.lang, "versionTitle": self.vtitle}) + assert self.full_version, "Failed to load Version record for {}, {}".format(self._oref.normal(), self.vtitle) + if self.versionSource: + self.full_version.versionSource = self.versionSource # hack + + content = self.full_version.sub_content(self._oref.index_node.version_address()) + self._pad(content) + self.full_version.sub_content(self._oref.index_node.version_address(), [i - 1 for i in self._oref.sections], self.text) + + self._check_available_text_pre_save() + + self.full_version.save() + self._oref.recalibrate_next_prev_refs(len(self.text)) + self._update_link_language_availability() + + return self + + def _check_available_text_pre_save(self): + """ + Stores the availability of this text in before a save is made, + so that we can know if segments have been added or deleted overall. + """ + self._available_text_pre_save = {} + langs_checked = [self.lang] # swtich to ["en", "he"] when global availability checks are needed + for lang in langs_checked: + try: + self._available_text_pre_save[lang] = self._oref.text(lang=lang).text + except NoVersionFoundError: + self._available_text_pre_save[lang] = [] + + def _check_available_segments_changed_post_save(self, lang=None): + """ + Returns a list of tuples containing a Ref and a boolean availability + for each Ref that was either made available or unavailble for `lang`. + If `lang` is None, returns changed availability across all langauges. + """ + if lang: + old_refs_available = self._text_to_ref_available(self._available_text_pre_save[self.lang]) + else: + # Looking for availability of in all langauges, merge results of Hebrew and English + old_en_refs_available = self._text_to_ref_available(self._available_text_pre_save["en"]) + old_he_refs_available = self._text_to_ref_available(self._available_text_pre_save["he"]) + zipped = list(itertools.zip_longest(old_en_refs_available, old_he_refs_available)) + old_refs_available = [] + for item in zipped: + en, he = item[0], item[1] + ref = en[0] if en else he[0] + old_refs_available.append((ref, (en and en[1] or he and he[1]))) + + new_refs_available = self._text_to_ref_available(self.text) + + changed = [] + zipped = list(itertools.zip_longest(old_refs_available, new_refs_available)) + for item in zipped: + old_text, new_text = item[0], item[1] + had_previously = old_text and old_text[1] + have_now = new_text and new_text[1] + + if not had_previously and have_now: + changed.append(new_text) + elif had_previously and not have_now: + # Current save is deleting a line of text, but it could still be + # available in a different version for this language. Check again. + if lang: + text_still_available = bool(old_text[0].text(lang=lang).text) + else: + text_still_available = bool(old_text[0].text("en").text) or bool(old_text[0].text("he").text) + if not text_still_available: + changed.append([old_text[0], False]) + + return changed + + def _update_link_language_availability(self): + """ + Check if current save has changed the overall availabilty of text for refs + in this language, pass refs to update revelant links if so. + """ + changed = self._check_available_segments_changed_post_save(lang=self.lang) + + if len(changed): + from . import link + for change in changed: + link.update_link_language_availabiliy(change[0], self.lang, change[1]) + + def get_language(self): + #method for getting a different lang attribue in old TextChunck and RangeText + #can be removed after merging and all uses can be replaced by self.acutal_lang + return self.lang + + class VirtualTextChunk(AbstractTextRecord): """ Delegated from TextChunk From 3207f2f53733bd6a6b5283916536ac31360f8e0e Mon Sep 17 00:00:00 2001 From: yishai Date: Tue, 13 Jun 2023 14:37:50 +0300 Subject: [PATCH 002/260] fix((TextChunck): change params order of TextRange. --- sefaria/model/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 87aa556be9..c731bce5a3 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1690,7 +1690,7 @@ class TextRange(AbstractTextRecord, metaclass=TextFamilyDelegator): text_attr = "text" - def __init__(self, oref, vtitle=None, actual_lang=None, **kwargs): + def __init__(self, oref, actual_lang=None, vtitle=None, **kwargs): #kwargs are only for supporting old TextChunck. should be removed after merging if isinstance(oref.index_node, JaggedArrayNode): self._oref = oref From 379122bfdd1e8d0aa65fa9c92bd1b378d9772619 Mon Sep 17 00:00:00 2001 From: yishai Date: Wed, 14 Jun 2023 12:40:33 +0300 Subject: [PATCH 003/260] chore((TextChunck): add TextRange to __init__. --- sefaria/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/model/__init__.py b/sefaria/model/__init__.py index afed915d2f..2764249067 100644 --- a/sefaria/model/__init__.py +++ b/sefaria/model/__init__.py @@ -18,7 +18,7 @@ from .history import History, HistorySet, log_add, log_delete, log_update, log_text from .schema import deserialize_tree, Term, TermSet, TermScheme, TermSchemeSet, TitledTreeNode, SchemaNode, \ ArrayMapNode, JaggedArrayNode, NumberedTitledTreeNode, NonUniqueTerm, NonUniqueTermSet -from .text import library, Index, IndexSet, Version, VersionSet, TextChunk, TextFamily, Ref, merge_texts +from .text import library, Index, IndexSet, Version, VersionSet, TextChunk, TextRange, TextFamily, Ref, merge_texts from .link import Link, LinkSet, get_link_counts, get_book_link_collection, get_book_category_linkset from .note import Note, NoteSet from .layer import Layer, LayerSet From f2a0651ab5afd64731e855ce00b0163de87a0cfc Mon Sep 17 00:00:00 2001 From: yishai Date: Wed, 14 Jun 2023 12:43:47 +0300 Subject: [PATCH 004/260] feat((TextChunck): new texts api. --- api/texts_api.py | 178 +++++++++++++++++++++++++++++++++++++++++++++++ api/views.py | 60 ++++++++++++++++ sefaria/urls.py | 2 + 3 files changed, 240 insertions(+) create mode 100644 api/texts_api.py create mode 100644 api/views.py diff --git a/api/texts_api.py b/api/texts_api.py new file mode 100644 index 0000000000..245ef0ebee --- /dev/null +++ b/api/texts_api.py @@ -0,0 +1,178 @@ +import django +django.setup() +from sefaria.model import * +from sefaria.datatype.jagged_array import JaggedArray + +class APITextsHandler(): + + def __init__(self, oref, versions_params): + self.versions_params = versions_params + self.oref = oref + self.required_versions = [] + self.handled_version_params = [] + self.all_versions = self.oref.versionset() + self.return_obj = {'versions': []} + + def append_required_versions(self, lang, vtitle=None): + if vtitle: + versions = [v for v in self.all_versions if v.actualLanguage == lang and v.versionTitle == vtitle] + else: + versions = [v for v in self.all_versions if v.actualLanguage == lang] + for version in versions: + if version not in self.required_versions: + self.required_versions.append(version) + + def handle_version_params(self, version_params): + if version_params in self.handled_version_params: + return + if '|' not in version_params: + lang, vtitle = version_params, '' + else: + lang, vtitle = version_params.split('|', 1) + vtitle = vtitle.replace('_', ' ') + if vtitle == 'all': + self.append_required_versions(lang) + else: + self.append_required_versions(lang, vtitle) + self.handled_version_params.append(version_params) + + def add_versions_to_obj(self): + for version in self.required_versions: + self.return_obj['versions'].append({ + "versionTitle": version.versionTitle, + "versionSource": version.versionSource, + "language": version.actualLanguage, #TODO - why do we need this? why in the server? + "languageCode": version.actualLanguage, + #TODO - do we need also direction? + "status": getattr(version, 'status', None), + "license": getattr(version, 'license', None), + "versionNotes": getattr(version, 'versionNotes', None), + "digitizedBySefaria": getattr(version, 'digitizedBySefaria', None), + "priority":getattr(version, 'priority', None), + "versionTitleInHebrew": getattr(version, 'versionTitleInHebrew', None), + "versionNotesInHebrew": getattr(version, 'versionNotesInHebrew', None), + "extendedNotes": getattr(version, 'extendedNotes', None), + "extendedNotesHebrew": getattr(version, 'extendedNotesHebrew', None), + "purchaseInformationImage": getattr(version, 'purchaseInformationImage', None), + "purchaseInformationURL": getattr(version, 'purchaseInformationURL', None), + "shortVersionTitle": getattr(version, 'shortVersionTitle', None), + "shortVersionTitleInHebrew": getattr(version, 'shortVersionTitleInHebrew', None), + "isBaseText": getattr(version, 'isBaseText', False), + "formatAsPoetry": getattr(version, 'formatAsPoetry', False), + "text": TextRange(self.oref, version.actualLanguage, version.versionTitle).text #TODO - current api returns text for sections. dowe want it? + }) + + def add_ref_data(self): + oref = self.oref + self.return_obj.update({ + 'ref': oref.normal(), + #TODO - why do we need all of those? + 'heRef': oref.he_normal(), + 'sections': oref.sections, + 'toSections': oref.toSections, + 'sectionRef': oref.section_ref().normal(), + 'heSectionRef': oref.section_ref().he_normal(), + 'firstAvailableSectionRef': oref.first_available_section_ref().normal(), + 'isSpanning': oref.is_spanning(), + 'spanningRefs': [r.normal() for r in oref.split_spanning_ref()], + 'next': oref.next_section_ref().normal(), #TODO - change names like 'next_section_ref'? the question is how much cilent code use this name + 'prev': oref.prev_section_ref().normal(), + 'title': oref.context_ref().normal(), + 'book': oref.book, #TODO - isnt it the same as title? + 'heTitle': oref.context_ref().he_normal(), + 'primary_category': oref.primary_category, + 'type': oref.primary_category, #same as primary category + }) + + def reduce_alts_to_ref(self): #TODO - copied from TextFamily. if we won't remove it, we should find some place for that + oref = self.oref + # Set up empty Array that mirrors text structure + alts_ja = JaggedArray() + for key, struct in oref.index.get_alt_structures().items(): + # Assuming these are in order, continue if it is before ours, break if we see one after + for n in struct.get_leaf_nodes(): + wholeRef = Ref(n.wholeRef).default_child_ref().as_ranged_segment_ref() + if wholeRef.ending_ref().precedes(oref): + continue + if wholeRef.starting_ref().follows(oref): + break + + # It's in our territory + wholeRefStart = wholeRef.starting_ref() + if oref.contains(wholeRefStart) and not wholeRefStart.contains(oref): + indxs = [k - 1 for k in wholeRefStart.in_terms_of(oref)] + val = {"en": [], "he": []} + try: + val = alts_ja.get_element(indxs) or val + except IndexError: + pass + val["en"] += [n.primary_title("en")] + val["he"] += [n.primary_title("he")] + val["whole"] = True + alts_ja.set_element(indxs, val) + + if getattr(n, "refs", None): + for i, r in enumerate(n.refs): + # hack to skip Rishon, skip empty refs + if i == 0 or not r: + continue + subRef = Ref(r) + subRefStart = subRef.starting_ref() + if oref.contains(subRefStart) and not subRefStart.contains(oref): + indxs = [k - 1 for k in subRefStart.in_terms_of(oref)] + val = {"en": [], "he": []} + try: + val = alts_ja.get_element(indxs) or val + except IndexError: + pass + val["en"] += [n.sectionString([i + 1], "en", title=False)] + val["he"] += [n.sectionString([i + 1], "he", title=False)] + alts_ja.set_element(indxs, val) + elif subRefStart.follows(oref): + break + + return alts_ja.array() + + def add_index_data(self): + index = self.oref.index + self.return_obj.update({ + 'indexTitle': index.title, + #TODO - those are attrs of index. it seems client can take them from there + 'categories': index.categories, + 'heIndexTitle': index.get_title('he'), + 'isComplex': index.is_complex(), + 'isDependant': index.is_dependant_text(), + 'order': getattr(index, 'order', ''), + # TODO - alts are reduced fron the index. I guess here we can't give the client to do the job but have to check + 'alts': self.reduce_alts_to_ref(), + }) + + def add_node_data(self): + inode = self.oref.index_node + # TODO - all those are attrs of node, so they are available also in the index. can the client take them from there? + if getattr(inode, "lengths", None): + self.return_obj["lengths"] = getattr(inode, "lengths") + if len(self.return_obj["lengths"]): + self.return_obj["length"] = self.return_obj["lengths"][0] + elif getattr(inode, "length", None): + self.return_obj["length"] = getattr(inode, "length") + self.return_obj.update({ + 'textDepth': getattr(inode, "depth", None), + 'sectionNames': getattr(inode, "sectionNames", None), + 'addressTypes': getattr(inode, "addressTypes", None), + 'heTitle': inode.full_title("he"), #TODO - is there use in this and the nexts? + 'titleVariants': inode.all_tree_titles("en"), + 'heTitleVariants': inode.all_tree_titles("he"), + # TODO - offsets are reduced fron the node. I guess here we can't give the client to do the job but have to check + # TODO - also this them for sections while this code for now returns the text according to segment refs + 'index_offsets_by_depth': inode.trim_index_offsets_by_sections(self.oref.sections, self.oref.toSections), + }) + + def get_versions_for_query(self): + for version_params in self.versions_params: + self.handle_version_params(version_params) + self.add_versions_to_obj() + self.add_ref_data() + self.add_index_data() + self.add_node_data() + return self.return_obj diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000000..4520ed72ff --- /dev/null +++ b/api/views.py @@ -0,0 +1,60 @@ +from django.http import Http404, QueryDict, HttpResponseBadRequest + +from sefaria.model import * +from sefaria.system.multiserver.coordinator import server_coordinator +from sefaria.settings import DISABLE_AUTOCOMPLETER, ENABLE_LINKER +from .texts_api import APITextsHandler +from sefaria.client.util import jsonResponse + +import structlog +logger = structlog.get_logger(__name__) + +#TODO - i've copied it from reader.views. i'm not sure what it does + +# # # +# Initialized cache library objects that depend on sefaria.model being completely loaded. +logger.info("Initializing library objects.") +logger.info("Initializing TOC Tree") +library.get_toc_tree() + +logger.info("Initializing Shared Cache") +library.init_shared_cache() + +if not DISABLE_AUTOCOMPLETER: + logger.info("Initializing Full Auto Completer") + library.build_full_auto_completer() + + logger.info("Initializing Ref Auto Completer") + library.build_ref_auto_completer() + + logger.info("Initializing Lexicon Auto Completers") + library.build_lexicon_auto_completers() + + logger.info("Initializing Cross Lexicon Auto Completer") + library.build_cross_lexicon_auto_completer() + + logger.info("Initializing Topic Auto Completer") + library.build_topic_auto_completer() + +if ENABLE_LINKER: + logger.info("Initializing Linker") + library.build_ref_resolver() + +if server_coordinator: + server_coordinator.connect() +# # # + +def get_texts(request, tref): + try: + oref = Ref.instantiate_ref_with_legacy_parse_fallback(tref) + except Exception as e: + return HttpResponseBadRequest(e) + cb = request.GET.get("callback", None) + if request.method == "GET": + versions_params = request.GET.getlist('version', []) + if not versions_params: + versions_params = ['source'] #TODO - or base? + handler = APITextsHandler(oref, versions_params) + data = handler.get_versions_for_query() + return jsonResponse(data, cb) + return jsonResponse({"error": "Unsupported HTTP method."}, cb) diff --git a/sefaria/urls.py b/sefaria/urls.py index 2232b6f16e..02741bc089 100644 --- a/sefaria/urls.py +++ b/sefaria/urls.py @@ -13,6 +13,7 @@ import sourcesheets.views as sheets_views import sefaria.gauth.views as gauth_views import django.contrib.auth.views as django_auth_views +import api.views as v3_views from sefaria.site.urls import site_urlpatterns @@ -146,6 +147,7 @@ url(r'^api/texts/modify-bulk/(?P.+)$', reader_views.modify_bulk_text_api), url(r'^api/texts/(?P<tref>.+)/(?P<lang>\w\w)/(?P<version>.+)$', reader_views.old_text_versions_api_redirect), url(r'^api/texts/(?P<tref>.+)$', reader_views.texts_api), + url(r'^api/v3/texts/(?P<tref>.+)$', v3_views.get_texts), url(r'^api/index/?$', reader_views.table_of_contents_api), url(r'^api/opensearch-suggestions/?$', reader_views.opensearch_suggestions_api), url(r'^api/index/titles/?$', reader_views.text_titles_api), From b735640c0c4dc0c8cd128c8aba88f5b786af94b2 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 15:22:55 +0300 Subject: [PATCH 005/260] fix((texts api): get best priority for default. --- api/texts_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/texts_api.py b/api/texts_api.py index 245ef0ebee..e7391ca8ee 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -14,10 +14,12 @@ def __init__(self, oref, versions_params): self.return_obj = {'versions': []} def append_required_versions(self, lang, vtitle=None): - if vtitle: + if vtitle and vtitle != 'all': versions = [v for v in self.all_versions if v.actualLanguage == lang and v.versionTitle == vtitle] else: versions = [v for v in self.all_versions if v.actualLanguage == lang] + if vtitle != 'all': + versions = [min(versions, key=lambda v: getattr(v, 'proiority', 100))] for version in versions: if version not in self.required_versions: self.required_versions.append(version) From 0fbf3df7dde33f9b66fa1a318649cbf6df0fdaec Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 15:37:02 +0300 Subject: [PATCH 006/260] feat(texts api): handle lang 'base' and 'source'. --- api/texts_api.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index e7391ca8ee..b43cb9a00a 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -6,7 +6,7 @@ class APITextsHandler(): def __init__(self, oref, versions_params): - self.versions_params = versions_params + self.versions_params = versions_params if versions_params else 'base' self.oref = oref self.required_versions = [] self.handled_version_params = [] @@ -14,10 +14,16 @@ def __init__(self, oref, versions_params): self.return_obj = {'versions': []} def append_required_versions(self, lang, vtitle=None): + if lang == 'base': + lang_condition = lambda v: getattr(v, 'isBaseText', False) == True + elif lang == 'source': + lang_condition = lambda v: getattr(v, 'isSource', False) == True + else: + lang_condition = lambda v: v.actualLanguage == lang if vtitle and vtitle != 'all': - versions = [v for v in self.all_versions if v.actualLanguage == lang and v.versionTitle == vtitle] + versions = [v for v in self.all_versions if lang_condition(v) and v.versionTitle == vtitle] else: - versions = [v for v in self.all_versions if v.actualLanguage == lang] + versions = [v for v in self.all_versions if lang_condition(v)] if vtitle != 'all': versions = [min(versions, key=lambda v: getattr(v, 'proiority', 100))] for version in versions: @@ -32,10 +38,7 @@ def handle_version_params(self, version_params): else: lang, vtitle = version_params.split('|', 1) vtitle = vtitle.replace('_', ' ') - if vtitle == 'all': - self.append_required_versions(lang) - else: - self.append_required_versions(lang, vtitle) + self.append_required_versions(lang, vtitle) self.handled_version_params.append(version_params) def add_versions_to_obj(self): From 820ea095ff56e51ce9f79b42c0ac682c333119c5 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 15:47:00 +0300 Subject: [PATCH 007/260] feat(texts api): default lang is base. --- api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/views.py b/api/views.py index 4520ed72ff..fec45b21e1 100644 --- a/api/views.py +++ b/api/views.py @@ -53,7 +53,7 @@ def get_texts(request, tref): if request.method == "GET": versions_params = request.GET.getlist('version', []) if not versions_params: - versions_params = ['source'] #TODO - or base? + versions_params = ['base'] #TODO - or base? handler = APITextsHandler(oref, versions_params) data = handler.get_versions_for_query() return jsonResponse(data, cb) From 13da51b42a51a99cbdef39d6c878207d343fd802 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 15:47:06 +0300 Subject: [PATCH 008/260] fix(texts api): dont search for best priority if there are no versions. --- api/texts_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index b43cb9a00a..dd5c2a03ec 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -6,7 +6,7 @@ class APITextsHandler(): def __init__(self, oref, versions_params): - self.versions_params = versions_params if versions_params else 'base' + self.versions_params = versions_params self.oref = oref self.required_versions = [] self.handled_version_params = [] @@ -24,7 +24,7 @@ def append_required_versions(self, lang, vtitle=None): versions = [v for v in self.all_versions if lang_condition(v) and v.versionTitle == vtitle] else: versions = [v for v in self.all_versions if lang_condition(v)] - if vtitle != 'all': + if vtitle != 'all' and versions: versions = [min(versions, key=lambda v: getattr(v, 'proiority', 100))] for version in versions: if version not in self.required_versions: From acaed171ac541c7928f9578ad67185a9f99e93d8 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 15:47:55 +0300 Subject: [PATCH 009/260] chore(texts api): remove unused imports. --- api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/views.py b/api/views.py index fec45b21e1..544c559b6e 100644 --- a/api/views.py +++ b/api/views.py @@ -1,4 +1,4 @@ -from django.http import Http404, QueryDict, HttpResponseBadRequest +from django.http import HttpResponseBadRequest from sefaria.model import * from sefaria.system.multiserver.coordinator import server_coordinator From 8324212b1f4d9f3248e136c52b554456e0ba59a3 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 18:26:00 +0300 Subject: [PATCH 010/260] chore(texts api): remove equal to True. --- api/texts_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index dd5c2a03ec..7bfa2f3ce4 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -15,9 +15,9 @@ def __init__(self, oref, versions_params): def append_required_versions(self, lang, vtitle=None): if lang == 'base': - lang_condition = lambda v: getattr(v, 'isBaseText', False) == True + lang_condition = lambda v: getattr(v, 'isBaseText', False) elif lang == 'source': - lang_condition = lambda v: getattr(v, 'isSource', False) == True + lang_condition = lambda v: getattr(v, 'isSource', False) else: lang_condition = lambda v: v.actualLanguage == lang if vtitle and vtitle != 'all': From 9ecddceea0668bd6c19f44cf3a44971c408abf72 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 19:11:16 +0300 Subject: [PATCH 011/260] chore(texts api): use version_list instead of versionSet - saves memory and more simple. --- api/texts_api.py | 49 +++++++++++++------------------------------ sefaria/model/text.py | 4 ++-- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index 7bfa2f3ce4..d0a7adbc21 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -8,27 +8,26 @@ class APITextsHandler(): def __init__(self, oref, versions_params): self.versions_params = versions_params self.oref = oref - self.required_versions = [] self.handled_version_params = [] - self.all_versions = self.oref.versionset() + self.all_versions = self.oref.version_list() self.return_obj = {'versions': []} def append_required_versions(self, lang, vtitle=None): if lang == 'base': - lang_condition = lambda v: getattr(v, 'isBaseText', False) + lang_condition = lambda v: v['isBaseText'] elif lang == 'source': - lang_condition = lambda v: getattr(v, 'isSource', False) + lang_condition = lambda v: v['isSource'] else: - lang_condition = lambda v: v.actualLanguage == lang + lang_condition = lambda v: v['actualLanguage'] == lang if vtitle and vtitle != 'all': - versions = [v for v in self.all_versions if lang_condition(v) and v.versionTitle == vtitle] + versions = [v for v in self.all_versions if lang_condition(v) and v['versionTitle'] == vtitle] else: versions = [v for v in self.all_versions if lang_condition(v)] if vtitle != 'all' and versions: - versions = [min(versions, key=lambda v: getattr(v, 'proiority', 100))] + versions = [max(versions, key=lambda v: int(v['proiority'] or 0))] for version in versions: - if version not in self.required_versions: - self.required_versions.append(version) + if version not in self.return_obj['versions']: + self.return_obj['versions'].append(version) def handle_version_params(self, version_params): if version_params in self.handled_version_params: @@ -41,31 +40,10 @@ def handle_version_params(self, version_params): self.append_required_versions(lang, vtitle) self.handled_version_params.append(version_params) - def add_versions_to_obj(self): - for version in self.required_versions: - self.return_obj['versions'].append({ - "versionTitle": version.versionTitle, - "versionSource": version.versionSource, - "language": version.actualLanguage, #TODO - why do we need this? why in the server? - "languageCode": version.actualLanguage, - #TODO - do we need also direction? - "status": getattr(version, 'status', None), - "license": getattr(version, 'license', None), - "versionNotes": getattr(version, 'versionNotes', None), - "digitizedBySefaria": getattr(version, 'digitizedBySefaria', None), - "priority":getattr(version, 'priority', None), - "versionTitleInHebrew": getattr(version, 'versionTitleInHebrew', None), - "versionNotesInHebrew": getattr(version, 'versionNotesInHebrew', None), - "extendedNotes": getattr(version, 'extendedNotes', None), - "extendedNotesHebrew": getattr(version, 'extendedNotesHebrew', None), - "purchaseInformationImage": getattr(version, 'purchaseInformationImage', None), - "purchaseInformationURL": getattr(version, 'purchaseInformationURL', None), - "shortVersionTitle": getattr(version, 'shortVersionTitle', None), - "shortVersionTitleInHebrew": getattr(version, 'shortVersionTitleInHebrew', None), - "isBaseText": getattr(version, 'isBaseText', False), - "formatAsPoetry": getattr(version, 'formatAsPoetry', False), - "text": TextRange(self.oref, version.actualLanguage, version.versionTitle).text #TODO - current api returns text for sections. dowe want it? - }) + def add_text_to_versions(self): + for version in self.return_obj['versions']: + version['text'] = TextRange(self.oref, version.actualLanguage, version.versionTitle).text + #TODO - current api returns text for sections. do we want it? def add_ref_data(self): oref = self.oref @@ -149,6 +127,7 @@ def add_index_data(self): 'isDependant': index.is_dependant_text(), 'order': getattr(index, 'order', ''), # TODO - alts are reduced fron the index. I guess here we can't give the client to do the job but have to check + # 'alts': self.reduce_alts_to_ref(), }) @@ -176,7 +155,7 @@ def add_node_data(self): def get_versions_for_query(self): for version_params in self.versions_params: self.handle_version_params(version_params) - self.add_versions_to_obj() + self.add_text_to_versions() self.add_ref_data() self.add_index_data() self.add_node_data() diff --git a/sefaria/model/text.py b/sefaria/model/text.py index c731bce5a3..57e6a16e03 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -4604,10 +4604,10 @@ def version_list(self): :return list: each list element is an object with keys 'versionTitle' and 'language' """ - fields = ["title", "versionTitle", "versionSource", "language", "status", "license", "versionNotes", + fields = ["title", "versionTitle", "versionSource", "language", 'actualLanguage', "status", "license", "versionNotes", "digitizedBySefaria", "priority", "versionTitleInHebrew", "versionNotesInHebrew", "extendedNotes", "extendedNotesHebrew", "purchaseInformationImage", "purchaseInformationURL", "shortVersionTitle", - "shortVersionTitleInHebrew", "isBaseText"] + "shortVersionTitleInHebrew", "isBaseText", 'isSource', 'formatAsPoetry'] versions = VersionSet(self.condition_query()) version_list = [] if self.is_book_level(): From 8195b9bad995d4e9f08d3432bed5971266257ac2 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 19:13:14 +0300 Subject: [PATCH 012/260] chore(texts api): add todo. --- api/texts_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index d0a7adbc21..602f29f607 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -127,7 +127,7 @@ def add_index_data(self): 'isDependant': index.is_dependant_text(), 'order': getattr(index, 'order', ''), # TODO - alts are reduced fron the index. I guess here we can't give the client to do the job but have to check - # + #TODO - also this did them for sections while this code for now returns the text according to segment refs 'alts': self.reduce_alts_to_ref(), }) @@ -148,7 +148,7 @@ def add_node_data(self): 'titleVariants': inode.all_tree_titles("en"), 'heTitleVariants': inode.all_tree_titles("he"), # TODO - offsets are reduced fron the node. I guess here we can't give the client to do the job but have to check - # TODO - also this them for sections while this code for now returns the text according to segment refs + # TODO - also this did them for sections while this code for now returns the text according to segment refs 'index_offsets_by_depth': inode.trim_index_offsets_by_sections(self.oref.sections, self.oref.toSections), }) From 79f18d2cd68ae2c847c83e413457c0139ce536e2 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 19:25:30 +0300 Subject: [PATCH 013/260] chore(texts api): remove redundant int. --- api/texts_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/texts_api.py b/api/texts_api.py index 602f29f607..e28d675f51 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -24,7 +24,7 @@ def append_required_versions(self, lang, vtitle=None): else: versions = [v for v in self.all_versions if lang_condition(v)] if vtitle != 'all' and versions: - versions = [max(versions, key=lambda v: int(v['proiority'] or 0))] + versions = [max(versions, key=lambda v: v['proiority'] or 0)] for version in versions: if version not in self.return_obj['versions']: self.return_obj['versions'].append(version) From cd7f89cb783ee78d1ec8b8654b04ba764538af4a Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 20:54:27 +0300 Subject: [PATCH 014/260] fix(texts api): pop title from version. --- api/texts_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/texts_api.py b/api/texts_api.py index e28d675f51..0b9b1aa716 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -42,6 +42,7 @@ def handle_version_params(self, version_params): def add_text_to_versions(self): for version in self.return_obj['versions']: + version.pop('title') version['text'] = TextRange(self.oref, version.actualLanguage, version.versionTitle).text #TODO - current api returns text for sections. do we want it? From a50c6bba76cb5a8439ec79a6748c07e4d156cee9 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 14 Jun 2023 20:57:44 +0300 Subject: [PATCH 015/260] chore(texts api): remove todo. --- api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/views.py b/api/views.py index 544c559b6e..43faca7a1e 100644 --- a/api/views.py +++ b/api/views.py @@ -53,7 +53,7 @@ def get_texts(request, tref): if request.method == "GET": versions_params = request.GET.getlist('version', []) if not versions_params: - versions_params = ['base'] #TODO - or base? + versions_params = ['base'] handler = APITextsHandler(oref, versions_params) data = handler.get_versions_for_query() return jsonResponse(data, cb) From 9fee80bdc32f1dd2444efd2d39f258d934112390 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Thu, 15 Jun 2023 08:53:10 +0300 Subject: [PATCH 016/260] fix(texts api): default lang en in TextRange. --- sefaria/model/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 57e6a16e03..3dd253a6a8 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1690,7 +1690,7 @@ class TextRange(AbstractTextRecord, metaclass=TextFamilyDelegator): text_attr = "text" - def __init__(self, oref, actual_lang=None, vtitle=None, **kwargs): + def __init__(self, oref, actual_lang='en', vtitle=None, **kwargs): #kwargs are only for supporting old TextChunck. should be removed after merging if isinstance(oref.index_node, JaggedArrayNode): self._oref = oref From 92cae3daa12c044ee2d51af9e903bbba7735776e Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Thu, 15 Jun 2023 09:09:52 +0300 Subject: [PATCH 017/260] feat(texts api): add direction to version and pop the old language. --- api/texts_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/texts_api.py b/api/texts_api.py index 0b9b1aa716..3982d82904 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -43,6 +43,8 @@ def handle_version_params(self, version_params): def add_text_to_versions(self): for version in self.return_obj['versions']: version.pop('title') + version['direction'] = 'ltr' if version['language'] == 'en' else 'he' + version.pop('language') version['text'] = TextRange(self.oref, version.actualLanguage, version.versionTitle).text #TODO - current api returns text for sections. do we want it? From c6b7725817759d67fbcc4395b775fef1068230ed Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Thu, 15 Jun 2023 09:10:46 +0300 Subject: [PATCH 018/260] fix(texts api): typo. --- api/texts_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/texts_api.py b/api/texts_api.py index 3982d82904..2946e3bc0a 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -24,7 +24,7 @@ def append_required_versions(self, lang, vtitle=None): else: versions = [v for v in self.all_versions if lang_condition(v)] if vtitle != 'all' and versions: - versions = [max(versions, key=lambda v: v['proiority'] or 0)] + versions = [max(versions, key=lambda v: v['priority'] or 0)] for version in versions: if version not in self.return_obj['versions']: self.return_obj['versions'].append(version) From dd4cb1623b21041f5dcf1405ebaabb677cb1d3ce Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Thu, 15 Jun 2023 12:37:55 +0300 Subject: [PATCH 019/260] feat(texts api): add errors to api response. --- api/texts_api.py | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index 2946e3bc0a..51dd23d805 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -2,15 +2,38 @@ django.setup() from sefaria.model import * from sefaria.datatype.jagged_array import JaggedArray +from enum import Enum class APITextsHandler(): + Direction = Enum('direction', ['rtl', 'ltr']) + def __init__(self, oref, versions_params): self.versions_params = versions_params + self.current_params = '' self.oref = oref self.handled_version_params = [] self.all_versions = self.oref.version_list() - self.return_obj = {'versions': []} + self.return_obj = {'versions': [], 'errors': []} + + def handle_errors(self, lang, vtitle): + print(f'"{self.current_params}", "{lang}", "{vtitle}"') + if lang == 'source': + code = 103 + message = f'We do not have the source text for {self.oref}' + elif vtitle and vtitle != 'all': + code = 101 + message = f'We do not have version named {vtitle} with language code {lang} for {self.oref}' + else: + code = 102 + availabe_langs = {v['actualLanguage'] for v in self.all_versions} + message = f'We do not have the code language you asked for {self.oref}. Available codes are {availabe_langs}' + self.return_obj['errors'].append({ + self.current_params: { + 'error_code': code, + 'message': message, + } + }) def append_required_versions(self, lang, vtitle=None): if lang == 'base': @@ -28,6 +51,8 @@ def append_required_versions(self, lang, vtitle=None): for version in versions: if version not in self.return_obj['versions']: self.return_obj['versions'].append(version) + if not versions: + self.handle_errors(lang, vtitle) def handle_version_params(self, version_params): if version_params in self.handled_version_params: @@ -43,16 +68,14 @@ def handle_version_params(self, version_params): def add_text_to_versions(self): for version in self.return_obj['versions']: version.pop('title') - version['direction'] = 'ltr' if version['language'] == 'en' else 'he' + version['direction'] = self.Direction.ltr.value if version['language'] == 'en' else self.Direction.rtl.value version.pop('language') - version['text'] = TextRange(self.oref, version.actualLanguage, version.versionTitle).text - #TODO - current api returns text for sections. do we want it? + version['text'] = TextRange(self.oref, version['actualLanguage'], version['versionTitle']).text def add_ref_data(self): oref = self.oref self.return_obj.update({ 'ref': oref.normal(), - #TODO - why do we need all of those? 'heRef': oref.he_normal(), 'sections': oref.sections, 'toSections': oref.toSections, @@ -61,10 +84,10 @@ def add_ref_data(self): 'firstAvailableSectionRef': oref.first_available_section_ref().normal(), 'isSpanning': oref.is_spanning(), 'spanningRefs': [r.normal() for r in oref.split_spanning_ref()], - 'next': oref.next_section_ref().normal(), #TODO - change names like 'next_section_ref'? the question is how much cilent code use this name + 'next': oref.next_section_ref().normal(), 'prev': oref.prev_section_ref().normal(), 'title': oref.context_ref().normal(), - 'book': oref.book, #TODO - isnt it the same as title? + 'book': oref.book, 'heTitle': oref.context_ref().he_normal(), 'primary_category': oref.primary_category, 'type': oref.primary_category, #same as primary category @@ -123,20 +146,16 @@ def add_index_data(self): index = self.oref.index self.return_obj.update({ 'indexTitle': index.title, - #TODO - those are attrs of index. it seems client can take them from there 'categories': index.categories, 'heIndexTitle': index.get_title('he'), 'isComplex': index.is_complex(), 'isDependant': index.is_dependant_text(), 'order': getattr(index, 'order', ''), - # TODO - alts are reduced fron the index. I guess here we can't give the client to do the job but have to check - #TODO - also this did them for sections while this code for now returns the text according to segment refs 'alts': self.reduce_alts_to_ref(), }) def add_node_data(self): inode = self.oref.index_node - # TODO - all those are attrs of node, so they are available also in the index. can the client take them from there? if getattr(inode, "lengths", None): self.return_obj["lengths"] = getattr(inode, "lengths") if len(self.return_obj["lengths"]): @@ -147,16 +166,15 @@ def add_node_data(self): 'textDepth': getattr(inode, "depth", None), 'sectionNames': getattr(inode, "sectionNames", None), 'addressTypes': getattr(inode, "addressTypes", None), - 'heTitle': inode.full_title("he"), #TODO - is there use in this and the nexts? + 'heTitle': inode.full_title("he"), 'titleVariants': inode.all_tree_titles("en"), 'heTitleVariants': inode.all_tree_titles("he"), - # TODO - offsets are reduced fron the node. I guess here we can't give the client to do the job but have to check - # TODO - also this did them for sections while this code for now returns the text according to segment refs 'index_offsets_by_depth': inode.trim_index_offsets_by_sections(self.oref.sections, self.oref.toSections), }) def get_versions_for_query(self): for version_params in self.versions_params: + self.current_params = version_params self.handle_version_params(version_params) self.add_text_to_versions() self.add_ref_data() From 07847e59210401afb8be6ad1898f8bfc7e777340 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 18 Jun 2023 09:11:07 +0300 Subject: [PATCH 020/260] refactor(Version): get fields for version_list from Version class atributes rather than another list. --- sefaria/model/text.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 3dd253a6a8..359b7e5365 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1340,6 +1340,8 @@ class Version(AbstractTextRecord, abst.AbstractMongoRecord, AbstractSchemaConten "hasManuallyWrappedRefs", # true for texts where refs were manually wrapped in a-tags. no need to run linker at run-time. "actualLanguage", "isBaseText", + 'isSource', + 'isBaseText2' #temp ] def __str__(self): @@ -4604,10 +4606,8 @@ def version_list(self): :return list: each list element is an object with keys 'versionTitle' and 'language' """ - fields = ["title", "versionTitle", "versionSource", "language", 'actualLanguage', "status", "license", "versionNotes", - "digitizedBySefaria", "priority", "versionTitleInHebrew", "versionNotesInHebrew", "extendedNotes", - "extendedNotesHebrew", "purchaseInformationImage", "purchaseInformationURL", "shortVersionTitle", - "shortVersionTitleInHebrew", "isBaseText", 'isSource', 'formatAsPoetry'] + fields = Version.optional_attrs + Version.required_attrs + fields.remove('chapter') versions = VersionSet(self.condition_query()) version_list = [] if self.is_book_level(): From 1cd1f1ed2899ea29f5294e78fca789183f6aec36 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 18 Jun 2023 09:22:29 +0300 Subject: [PATCH 021/260] chore(api texts): change isBaseText to isBaseText2 (temporal name). --- api/texts_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/texts_api.py b/api/texts_api.py index 51dd23d805..4365a4a090 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -37,7 +37,7 @@ def handle_errors(self, lang, vtitle): def append_required_versions(self, lang, vtitle=None): if lang == 'base': - lang_condition = lambda v: v['isBaseText'] + lang_condition = lambda v: v['isBaseText2'] #temporal name elif lang == 'source': lang_condition = lambda v: v['isSource'] else: From 16852b0971eda9d52e88d058f88d1d35facaf278 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 18 Jun 2023 10:33:41 +0300 Subject: [PATCH 022/260] chore(api texts): add underscore to private functions. --- api/texts_api.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index 4365a4a090..9b7b88644f 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -16,7 +16,7 @@ def __init__(self, oref, versions_params): self.all_versions = self.oref.version_list() self.return_obj = {'versions': [], 'errors': []} - def handle_errors(self, lang, vtitle): + def _handle_errors(self, lang, vtitle): print(f'"{self.current_params}", "{lang}", "{vtitle}"') if lang == 'source': code = 103 @@ -35,7 +35,7 @@ def handle_errors(self, lang, vtitle): } }) - def append_required_versions(self, lang, vtitle=None): + def _append_required_versions(self, lang, vtitle=None): if lang == 'base': lang_condition = lambda v: v['isBaseText2'] #temporal name elif lang == 'source': @@ -54,7 +54,7 @@ def append_required_versions(self, lang, vtitle=None): if not versions: self.handle_errors(lang, vtitle) - def handle_version_params(self, version_params): + def _handle_version_params(self, version_params): if version_params in self.handled_version_params: return if '|' not in version_params: @@ -65,14 +65,14 @@ def handle_version_params(self, version_params): self.append_required_versions(lang, vtitle) self.handled_version_params.append(version_params) - def add_text_to_versions(self): + def _add_text_to_versions(self): for version in self.return_obj['versions']: version.pop('title') version['direction'] = self.Direction.ltr.value if version['language'] == 'en' else self.Direction.rtl.value version.pop('language') version['text'] = TextRange(self.oref, version['actualLanguage'], version['versionTitle']).text - def add_ref_data(self): + def _add_ref_data(self): oref = self.oref self.return_obj.update({ 'ref': oref.normal(), @@ -93,7 +93,7 @@ def add_ref_data(self): 'type': oref.primary_category, #same as primary category }) - def reduce_alts_to_ref(self): #TODO - copied from TextFamily. if we won't remove it, we should find some place for that + def _reduce_alts_to_ref(self): #TODO - copied from TextFamily. if we won't remove it, we should find some place for that oref = self.oref # Set up empty Array that mirrors text structure alts_ja = JaggedArray() @@ -142,7 +142,7 @@ def reduce_alts_to_ref(self): #TODO - copied from TextFamily. if we won't remove return alts_ja.array() - def add_index_data(self): + def _add_index_data(self): index = self.oref.index self.return_obj.update({ 'indexTitle': index.title, @@ -154,7 +154,7 @@ def add_index_data(self): 'alts': self.reduce_alts_to_ref(), }) - def add_node_data(self): + def _add_node_data(self): inode = self.oref.index_node if getattr(inode, "lengths", None): self.return_obj["lengths"] = getattr(inode, "lengths") From 37d2459a0cd2ce771363198e4c587708fbd53eac Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 18 Jun 2023 14:55:54 +0300 Subject: [PATCH 023/260] fix(api texts): add underscore to calls. --- api/texts_api.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index 9b7b88644f..98a2ffe617 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -52,7 +52,7 @@ def _append_required_versions(self, lang, vtitle=None): if version not in self.return_obj['versions']: self.return_obj['versions'].append(version) if not versions: - self.handle_errors(lang, vtitle) + self._handle_errors(lang, vtitle) def _handle_version_params(self, version_params): if version_params in self.handled_version_params: @@ -62,7 +62,7 @@ def _handle_version_params(self, version_params): else: lang, vtitle = version_params.split('|', 1) vtitle = vtitle.replace('_', ' ') - self.append_required_versions(lang, vtitle) + self._append_required_versions(lang, vtitle) self.handled_version_params.append(version_params) def _add_text_to_versions(self): @@ -151,7 +151,7 @@ def _add_index_data(self): 'isComplex': index.is_complex(), 'isDependant': index.is_dependant_text(), 'order': getattr(index, 'order', ''), - 'alts': self.reduce_alts_to_ref(), + 'alts': self._reduce_alts_to_ref(), }) def _add_node_data(self): @@ -175,9 +175,9 @@ def _add_node_data(self): def get_versions_for_query(self): for version_params in self.versions_params: self.current_params = version_params - self.handle_version_params(version_params) - self.add_text_to_versions() - self.add_ref_data() - self.add_index_data() - self.add_node_data() + self._handle_version_params(version_params) + self._add_text_to_versions() + self._add_ref_data() + self._add_index_data() + self._add_node_data() return self.return_obj From f1c881440637465286ba954fb3b957e5449a21f8 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 18 Jun 2023 19:25:32 +0300 Subject: [PATCH 024/260] fix(api texts): sections and toSections for Talmud addressType, add collective title. --- api/texts_api.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index 98a2ffe617..fd8d0a4f74 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -2,6 +2,7 @@ django.setup() from sefaria.model import * from sefaria.datatype.jagged_array import JaggedArray +from sefaria.utils.hebrew import hebrew_term from enum import Enum class APITextsHandler(): @@ -77,16 +78,16 @@ def _add_ref_data(self): self.return_obj.update({ 'ref': oref.normal(), 'heRef': oref.he_normal(), - 'sections': oref.sections, - 'toSections': oref.toSections, + 'sections': oref.normal_sections(), #that means it will be string. in the previous api talmud sections were strings while integers remained integets. this is more consistent but we should check it works + 'toSections': oref.normal_toSections(), 'sectionRef': oref.section_ref().normal(), 'heSectionRef': oref.section_ref().he_normal(), 'firstAvailableSectionRef': oref.first_available_section_ref().normal(), 'isSpanning': oref.is_spanning(), 'spanningRefs': [r.normal() for r in oref.split_spanning_ref()], 'next': oref.next_section_ref().normal(), - 'prev': oref.prev_section_ref().normal(), - 'title': oref.context_ref().normal(), + 'prev': oref.prev_section_ref().normal() if oref.prev_section_ref() else None, + 'title': oref.context_ref().normal() if oref.next_section_ref() else None, 'book': oref.book, 'heTitle': oref.context_ref().he_normal(), 'primary_category': oref.primary_category, @@ -151,6 +152,8 @@ def _add_index_data(self): 'isComplex': index.is_complex(), 'isDependant': index.is_dependant_text(), 'order': getattr(index, 'order', ''), + 'collectiveTitle': getattr(index, 'collective_title', ''), + 'heCollectiveTitle': hebrew_term(getattr(index, 'collective_title', '')), 'alts': self._reduce_alts_to_ref(), }) From 1d8ae8d775256c16a79d8e3b15cdbd72be3762c5 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 09:48:59 +0300 Subject: [PATCH 025/260] chore(api texts): return json in error 400. --- api/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/views.py b/api/views.py index 43faca7a1e..55e818aef5 100644 --- a/api/views.py +++ b/api/views.py @@ -1,5 +1,5 @@ +import json from django.http import HttpResponseBadRequest - from sefaria.model import * from sefaria.system.multiserver.coordinator import server_coordinator from sefaria.settings import DISABLE_AUTOCOMPLETER, ENABLE_LINKER @@ -48,7 +48,7 @@ def get_texts(request, tref): try: oref = Ref.instantiate_ref_with_legacy_parse_fallback(tref) except Exception as e: - return HttpResponseBadRequest(e) + return HttpResponseBadRequest(json.dumps({'error': getattr(e, 'message', str(e))}, ensure_ascii=False)) cb = request.GET.get("callback", None) if request.method == "GET": versions_params = request.GET.getlist('version', []) From 0ffbf93387a4d6604bfc7c5d15fb803549dcd11d Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 11:20:36 +0300 Subject: [PATCH 026/260] chore(api texts): remove print. --- api/texts_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/texts_api.py b/api/texts_api.py index fd8d0a4f74..f2e43d4e51 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -18,7 +18,6 @@ def __init__(self, oref, versions_params): self.return_obj = {'versions': [], 'errors': []} def _handle_errors(self, lang, vtitle): - print(f'"{self.current_params}", "{lang}", "{vtitle}"') if lang == 'source': code = 103 message = f'We do not have the source text for {self.oref}' From f5d743278a21eb0bcae55e191b8aa5d1ffcf0d59 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 12:17:40 +0300 Subject: [PATCH 027/260] chore(api texts): sort avaailabe langs in error message (for stable message). --- api/texts_api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index f2e43d4e51..c02281a63e 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -18,7 +18,10 @@ def __init__(self, oref, versions_params): self.return_obj = {'versions': [], 'errors': []} def _handle_errors(self, lang, vtitle): - if lang == 'source': + if self.oref.is_empty(): + code = 104 + message = f'The ref {self.oref} is empty' + elif lang == 'source': code = 103 message = f'We do not have the source text for {self.oref}' elif vtitle and vtitle != 'all': @@ -27,7 +30,7 @@ def _handle_errors(self, lang, vtitle): else: code = 102 availabe_langs = {v['actualLanguage'] for v in self.all_versions} - message = f'We do not have the code language you asked for {self.oref}. Available codes are {availabe_langs}' + message = f'We do not have the code language you asked for {self.oref}. Available codes are {sorted(availabe_langs)}' self.return_obj['errors'].append({ self.current_params: { 'error_code': code, From 681806fa2ffa4091efe22da7145e06a5e144d1af Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 12:55:57 +0300 Subject: [PATCH 028/260] chore(api texts): tests. --- api/tests.py | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 api/tests.py diff --git a/api/tests.py b/api/tests.py new file mode 100644 index 0000000000..643689df5e --- /dev/null +++ b/api/tests.py @@ -0,0 +1,132 @@ +from django.test.client import Client +import django +django.setup() +from reader.tests import SefariaTestCase +import json + +c = Client() + +class APITextsTests(SefariaTestCase): + + def test_api_get_text_default(self): + response = c.get('/api/v3/texts/Genesis.1') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertTrue(len(data["versions"]) == 1) + self.assertTrue(data["versions"][0]['actualLanguage'] == 'he') + self.assertEqual(data["book"], "Genesis") + self.assertEqual(data["categories"], ["Tanakh", "Torah"]) + self.assertEqual(data["sections"], ['1']) + self.assertEqual(data["toSections"], ['1']) + + def test_api_get_text_source_all(self): + response = c.get('/api/v3/texts/Shabbat.22a?version=source|all') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertTrue(len(data["versions"]) > 1) + self.assertTrue(all(v['actualLanguage'] == 'he' for v in data["versions"])) + self.assertEqual(data["book"], "Shabbat") + self.assertEqual(data["categories"], ["Talmud", "Bavli", "Seder Moed"]) + self.assertEqual(data["sections"], ["22a"]) + self.assertEqual(data["toSections"], ["22a"]) + + def test_api_get_text_lang_all(self): + response = c.get('/api/v3/texts/Rashi_on_Genesis.2.3?version=en|all') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertTrue(len(data["versions"]) > 1) + self.assertTrue(all(v['actualLanguage'] == 'en' for v in data["versions"])) + self.assertEqual(data["book"], "Rashi on Genesis") + self.assertEqual(data["collectiveTitle"], "Rashi") + self.assertEqual(data["categories"], ["Tanakh", "Rishonim on Tanakh", "Rashi", "Torah"]) + self.assertEqual(data["sections"], ['2', '3']) + self.assertEqual(data["toSections"], ['2', '3']) + + def test_api_get_text_specific(self): + response = c.get('/api/v3/texts/Tosafot_on_Sukkah.2a.4.1?version=he|Vilna_Edition') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data["versions"]), 1) + self.assertEqual(data["versions"][0]['actualLanguage'], 'he') + self.assertEqual(data["versions"][0]['versionTitle'], 'Vilna Edition') + self.assertEqual(data["book"], "Tosafot on Sukkah") + self.assertEqual(data["collectiveTitle"], "Tosafot") + self.assertEqual(data["categories"], ["Talmud", "Bavli", "Rishonim on Talmud", "Tosafot", "Seder Moed"]) + self.assertEqual(data["sections"], ["2a", '4', '1']) + self.assertEqual(data["toSections"], ["2a", '4', '1']) + + def test_api_get_text_base_all(self): + response = c.get('/api/v3/texts/Genesis.1?version=base|all') + data = json.loads(response.content) + self.assertTrue(len(data["versions"]) > 3) + self.assertTrue(all(v['actualLanguage'] == 'he' for v in data["versions"])) + + def test_api_get_text_two_params(self): + response = c.get('/api/v3/texts/Genesis.1?version=he|Tanach with Nikkud&version=en|all') + data = json.loads(response.content) + self.assertTrue(len(data["versions"]) > 7) + self.assertEqual(data["versions"][0]['actualLanguage'], 'he') + self.assertTrue(all(v['actualLanguage'] == 'en' for v in data["versions"][1:])) + + def test_api_get_text_range(self): + response = c.get('/api/v3/texts/Job.5.2-4') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(data["sections"], ['5', '2']) + self.assertEqual(data["toSections"], ['5', '4']) + + def test_api_get_text_bad_text(self): + response = c.get('/api/v3/texts/Life_of_Pi.13.13') + self.assertEqual(400, response.status_code) + data = json.loads(response.content) + self.assertEqual(data["error"], "Could not find title in reference: Life of Pi.13.13") + + def test_api_get_text_out_of_bound(self): + response = c.get('/api/v3/texts/Genesis.999') + data = json.loads(response.content) + self.assertEqual(data["error"], "Genesis ends at Chapter 50.") + + def test_api_get_text_too_many_hyphens(self): + response = c.get('/api/v3/texts/Genesis.9-4-5') + data = json.loads(response.content) + self.assertEqual(data["error"], "Couldn't understand ref 'Genesis.9-4-5' (too many -'s).") + + def test_api_get_text_bad_sections(self): + response = c.get('/api/v3/texts/Job.6-X') + self.assertEqual(400, response.status_code) + data = json.loads(response.content) + self.assertEqual(data["error"], "Couldn't understand text sections: 'Job.6-X'.") + + def test_api_get_text_no_source(self): + response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=en|Brenton's_Septuagint&version=source") + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data["versions"]), 1) + self.assertEqual(data['errors'][0]['source']['error_code'], 103) + self.assertEqual(data['errors'][0]['source']['message'], 'We do not have the source text for The Book of Maccabees I 1') + + def test_api_get_text_no_language(self): + response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=en|Brenton's_Septuagint&version=sgrg|all") + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data["versions"]), 1) + self.assertEqual(data['errors'][0]['sgrg|all']['error_code'], 102) + self.assertEqual(data['errors'][0]['sgrg|all']['message'], + "We do not have the code language you asked for The Book of Maccabees I 1. Available codes are ['en', 'he']") + + def test_api_get_text_no_version(self): + response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=en|Brenton's_Septuagint&version=he|Kishkoosh") + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data["versions"]), 1) + self.assertEqual(data['errors'][0]['he|Kishkoosh']['error_code'], 101) + self.assertEqual(data['errors'][0]['he|Kishkoosh']['message'], + 'We do not have version named Kishkoosh with language code he for The Book of Maccabees I 1') + + + def test_api_get_text_empty_ref(self): + response = c.get("/api/v3/texts/Berakhot.1a") + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(data['errors'][0]['base']['error_code'], 104) + self.assertEqual(data['errors'][0]['base']['message'], 'The ref Berakhot 1a is empty') From d3ed3a50b01edceccfb023ad030e4a22789465a9 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 16:24:06 +0300 Subject: [PATCH 029/260] refactor(api texts): class for errors. --- api/api_errors.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ api/texts_api.py | 18 ++++++------------ 2 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 api/api_errors.py diff --git a/api/api_errors.py b/api/api_errors.py new file mode 100644 index 0000000000..04572a24b0 --- /dev/null +++ b/api/api_errors.py @@ -0,0 +1,44 @@ +import django +django.setup() +from sefaria.model import * +from typing import List + + +class APIError(): + + def __init__(self, error_code=0, message=''): + self.error_code = error_code + self.message = message + + def get_dict(self): + return {'error_code': self.error_code, + 'message': self.message} + + +class NoVersionError(APIError): + + def __init__(self, oref: Ref, vtitle: str, lang: str): + self.error_code = 101 + self.message = f'We do not have version named {vtitle} with language code {lang} for {oref}' + + +class NoLanguageVersionError(APIError): + + def __init__(self, oref: Ref, langs: List[str]): + self.error_code = 102 + self.message = f'We do not have the code language you asked for {oref}. Available codes are {langs}' + + +class NoSourceTextError(APIError): + + def __init__(self, oref: Ref): + self.error_code = 103 + self.message = f'We do not have the source text for {oref}' + + + +class RefIsEmptyError(APIError): + + def __init__(self, oref: Ref): + self.error_code = 104 + self.message = f'The ref {oref} is empty' diff --git a/api/texts_api.py b/api/texts_api.py index c02281a63e..cef866454e 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -4,6 +4,7 @@ from sefaria.datatype.jagged_array import JaggedArray from sefaria.utils.hebrew import hebrew_term from enum import Enum +from .api_errors import * class APITextsHandler(): @@ -19,23 +20,16 @@ def __init__(self, oref, versions_params): def _handle_errors(self, lang, vtitle): if self.oref.is_empty(): - code = 104 - message = f'The ref {self.oref} is empty' + error = RefIsEmptyError(self.oref) elif lang == 'source': - code = 103 - message = f'We do not have the source text for {self.oref}' + error = NoSourceTextError(self.oref) elif vtitle and vtitle != 'all': - code = 101 - message = f'We do not have version named {vtitle} with language code {lang} for {self.oref}' + error = NoVersionError(self.oref, vtitle, lang) else: - code = 102 availabe_langs = {v['actualLanguage'] for v in self.all_versions} - message = f'We do not have the code language you asked for {self.oref}. Available codes are {sorted(availabe_langs)}' + error = NoLanguageVersionError(self.oref, sorted(availabe_langs)) self.return_obj['errors'].append({ - self.current_params: { - 'error_code': code, - 'message': message, - } + self.current_params: error.get_dict() }) def _append_required_versions(self, lang, vtitle=None): From d22baa8b164cb8baf840ca1914e8afdd6e85488e Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 16:53:12 +0300 Subject: [PATCH 030/260] refactor(api texts): change magic string to vars. --- api/texts_api.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index cef866454e..fc1e381137 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -9,6 +9,9 @@ class APITextsHandler(): Direction = Enum('direction', ['rtl', 'ltr']) + ALL = 'all' + BASE = 'base' + SOURCE = 'source' def __init__(self, oref, versions_params): self.versions_params = versions_params @@ -21,9 +24,9 @@ def __init__(self, oref, versions_params): def _handle_errors(self, lang, vtitle): if self.oref.is_empty(): error = RefIsEmptyError(self.oref) - elif lang == 'source': + elif lang == self.SOURCE: error = NoSourceTextError(self.oref) - elif vtitle and vtitle != 'all': + elif vtitle and vtitle != self.ALL: error = NoVersionError(self.oref, vtitle, lang) else: availabe_langs = {v['actualLanguage'] for v in self.all_versions} @@ -33,17 +36,17 @@ def _handle_errors(self, lang, vtitle): }) def _append_required_versions(self, lang, vtitle=None): - if lang == 'base': + if lang == self.BASE: lang_condition = lambda v: v['isBaseText2'] #temporal name - elif lang == 'source': + elif lang == self.SOURCE: lang_condition = lambda v: v['isSource'] else: lang_condition = lambda v: v['actualLanguage'] == lang - if vtitle and vtitle != 'all': + if vtitle and vtitle != self.ALL: versions = [v for v in self.all_versions if lang_condition(v) and v['versionTitle'] == vtitle] else: versions = [v for v in self.all_versions if lang_condition(v)] - if vtitle != 'all' and versions: + if vtitle != self.ALL and versions: versions = [max(versions, key=lambda v: v['priority'] or 0)] for version in versions: if version not in self.return_obj['versions']: From 8c63e33bd1605d47357bb18575c3fc7eee3c1615 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 16:53:55 +0300 Subject: [PATCH 031/260] chore(api texts): remove import. --- api/texts_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/texts_api.py b/api/texts_api.py index fc1e381137..595a68e3d5 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -1,6 +1,5 @@ import django django.setup() -from sefaria.model import * from sefaria.datatype.jagged_array import JaggedArray from sefaria.utils.hebrew import hebrew_term from enum import Enum From a0a6e208d13a9d3ca50dd3755e4158c34d441f50 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 17:17:30 +0300 Subject: [PATCH 032/260] chore(api texts): splitting params by a helper function. --- api/helper.py | 8 ++++++++ api/texts_api.py | 8 +++----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 api/helper.py diff --git a/api/helper.py b/api/helper.py new file mode 100644 index 0000000000..aa9b781263 --- /dev/null +++ b/api/helper.py @@ -0,0 +1,8 @@ +from typing import List + +def split_at_pipe_with_default(string: str, list_length: int, defaults: List[str]) -> List[str]: + #length of defaults should be one less than list_length + substrings = string.split('|', list_length-1) + while len(substrings) < list_length: + substrings.append(defaults.pop()) + return substrings diff --git a/api/texts_api.py b/api/texts_api.py index 595a68e3d5..f17c6cda78 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -4,6 +4,7 @@ from sefaria.utils.hebrew import hebrew_term from enum import Enum from .api_errors import * +from .helper import split_at_pipe_with_default class APITextsHandler(): @@ -56,11 +57,8 @@ def _append_required_versions(self, lang, vtitle=None): def _handle_version_params(self, version_params): if version_params in self.handled_version_params: return - if '|' not in version_params: - lang, vtitle = version_params, '' - else: - lang, vtitle = version_params.split('|', 1) - vtitle = vtitle.replace('_', ' ') + lang, vtitle = split_at_pipe_with_default(version_params, 2, ['']) + vtitle = vtitle.replace('_', ' ') self._append_required_versions(lang, vtitle) self.handled_version_params.append(version_params) From 082d2b5b3ccd6d0dfef052c68ae84091b9cfc1da Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 17:27:56 +0300 Subject: [PATCH 033/260] chore(api texts): add type hints. --- api/api_errors.py | 2 +- api/texts_api.py | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/api/api_errors.py b/api/api_errors.py index 04572a24b0..c1b7b40af7 100644 --- a/api/api_errors.py +++ b/api/api_errors.py @@ -10,7 +10,7 @@ def __init__(self, error_code=0, message=''): self.error_code = error_code self.message = message - def get_dict(self): + def get_dict(self) -> dict: return {'error_code': self.error_code, 'message': self.message} diff --git a/api/texts_api.py b/api/texts_api.py index f17c6cda78..7b513f5fa3 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -1,10 +1,12 @@ import django django.setup() +from sefaria.model import * from sefaria.datatype.jagged_array import JaggedArray from sefaria.utils.hebrew import hebrew_term from enum import Enum from .api_errors import * from .helper import split_at_pipe_with_default +from typing import List class APITextsHandler(): @@ -13,7 +15,7 @@ class APITextsHandler(): BASE = 'base' SOURCE = 'source' - def __init__(self, oref, versions_params): + def __init__(self, oref: Ref, versions_params: List[str]): self.versions_params = versions_params self.current_params = '' self.oref = oref @@ -21,7 +23,7 @@ def __init__(self, oref, versions_params): self.all_versions = self.oref.version_list() self.return_obj = {'versions': [], 'errors': []} - def _handle_errors(self, lang, vtitle): + def _handle_errors(self, lang: str, vtitle: str) -> None: if self.oref.is_empty(): error = RefIsEmptyError(self.oref) elif lang == self.SOURCE: @@ -35,7 +37,7 @@ def _handle_errors(self, lang, vtitle): self.current_params: error.get_dict() }) - def _append_required_versions(self, lang, vtitle=None): + def _append_required_versions(self, lang: str, vtitle=None) -> None: if lang == self.BASE: lang_condition = lambda v: v['isBaseText2'] #temporal name elif lang == self.SOURCE: @@ -54,7 +56,7 @@ def _append_required_versions(self, lang, vtitle=None): if not versions: self._handle_errors(lang, vtitle) - def _handle_version_params(self, version_params): + def _handle_version_params(self, version_params: str) -> None: if version_params in self.handled_version_params: return lang, vtitle = split_at_pipe_with_default(version_params, 2, ['']) @@ -62,14 +64,14 @@ def _handle_version_params(self, version_params): self._append_required_versions(lang, vtitle) self.handled_version_params.append(version_params) - def _add_text_to_versions(self): + def _add_text_to_versions(self) -> None: for version in self.return_obj['versions']: version.pop('title') version['direction'] = self.Direction.ltr.value if version['language'] == 'en' else self.Direction.rtl.value version.pop('language') version['text'] = TextRange(self.oref, version['actualLanguage'], version['versionTitle']).text - def _add_ref_data(self): + def _add_ref_data(self) -> None: oref = self.oref self.return_obj.update({ 'ref': oref.normal(), @@ -90,7 +92,7 @@ def _add_ref_data(self): 'type': oref.primary_category, #same as primary category }) - def _reduce_alts_to_ref(self): #TODO - copied from TextFamily. if we won't remove it, we should find some place for that + def _reduce_alts_to_ref(self) -> dict: #TODO - copied from TextFamily. if we won't remove it, we should find some place for that oref = self.oref # Set up empty Array that mirrors text structure alts_ja = JaggedArray() @@ -139,7 +141,7 @@ def _reduce_alts_to_ref(self): #TODO - copied from TextFamily. if we won't remov return alts_ja.array() - def _add_index_data(self): + def _add_index_data(self) -> None: index = self.oref.index self.return_obj.update({ 'indexTitle': index.title, @@ -153,7 +155,7 @@ def _add_index_data(self): 'alts': self._reduce_alts_to_ref(), }) - def _add_node_data(self): + def _add_node_data(self) -> None: inode = self.oref.index_node if getattr(inode, "lengths", None): self.return_obj["lengths"] = getattr(inode, "lengths") @@ -171,7 +173,7 @@ def _add_node_data(self): 'index_offsets_by_depth': inode.trim_index_offsets_by_sections(self.oref.sections, self.oref.toSections), }) - def get_versions_for_query(self): + def get_versions_for_query(self) -> dict: for version_params in self.versions_params: self.current_params = version_params self._handle_version_params(version_params) From e702160432358b0f4ef53814d85fe05c0c401756 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 17:42:42 +0300 Subject: [PATCH 034/260] docs(api texts): documentation for APITextsHandler and _reduce_alts_to_ref. --- api/texts_api.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/api/texts_api.py b/api/texts_api.py index 7b513f5fa3..b47732ef8a 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -9,6 +9,21 @@ from typing import List class APITextsHandler(): + """ + process api calls for text + an api call contains ref and list of version_params + a version params is string divided by pipe as 'lang|vtitle' + lang is our code language. special values are: + source - for versions in the source language of the text + base - as source but with falling to the 'nearest' to source, or what we have defined as such + vtitle is the exact versionTitle. special values are: + no vtitle - the version with the max priority attr of the specified language + all - all versions of the specified language + return_obj is dict that includes in its root: + ref and index data + 'versions' - list of versions details and text + 'errors' - for any version_params that had an error + """ Direction = Enum('direction', ['rtl', 'ltr']) ALL = 'all' @@ -93,6 +108,10 @@ def _add_ref_data(self) -> None: }) def _reduce_alts_to_ref(self) -> dict: #TODO - copied from TextFamily. if we won't remove it, we should find some place for that + """ + this function takes the index's alt_structs and reduce it to the relevant ref + it is necessary for the client side + """ oref = self.oref # Set up empty Array that mirrors text structure alts_ja = JaggedArray() From e796bf2540617807851001e4e70a8d1ba70af72e Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 19 Jun 2023 17:52:13 +0300 Subject: [PATCH 035/260] chore(api texts): remove all redundant code from views beginning. --- api/views.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/api/views.py b/api/views.py index 55e818aef5..c7709a4abb 100644 --- a/api/views.py +++ b/api/views.py @@ -1,48 +1,9 @@ import json from django.http import HttpResponseBadRequest from sefaria.model import * -from sefaria.system.multiserver.coordinator import server_coordinator -from sefaria.settings import DISABLE_AUTOCOMPLETER, ENABLE_LINKER from .texts_api import APITextsHandler from sefaria.client.util import jsonResponse -import structlog -logger = structlog.get_logger(__name__) - -#TODO - i've copied it from reader.views. i'm not sure what it does - -# # # -# Initialized cache library objects that depend on sefaria.model being completely loaded. -logger.info("Initializing library objects.") -logger.info("Initializing TOC Tree") -library.get_toc_tree() - -logger.info("Initializing Shared Cache") -library.init_shared_cache() - -if not DISABLE_AUTOCOMPLETER: - logger.info("Initializing Full Auto Completer") - library.build_full_auto_completer() - - logger.info("Initializing Ref Auto Completer") - library.build_ref_auto_completer() - - logger.info("Initializing Lexicon Auto Completers") - library.build_lexicon_auto_completers() - - logger.info("Initializing Cross Lexicon Auto Completer") - library.build_cross_lexicon_auto_completer() - - logger.info("Initializing Topic Auto Completer") - library.build_topic_auto_completer() - -if ENABLE_LINKER: - logger.info("Initializing Linker") - library.build_ref_resolver() - -if server_coordinator: - server_coordinator.connect() -# # # def get_texts(request, tref): try: From 6cc9fb117cfd0f5233d6739db617af1f4cd9016b Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 20 Jun 2023 10:34:55 +0300 Subject: [PATCH 036/260] chore(api texts): remove all redundant code from views beginning. --- api/texts_api.py | 4 +--- sefaria/model/text.py | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index b47732ef8a..86aa281c63 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -25,7 +25,6 @@ class APITextsHandler(): 'errors' - for any version_params that had an error """ - Direction = Enum('direction', ['rtl', 'ltr']) ALL = 'all' BASE = 'base' SOURCE = 'source' @@ -82,8 +81,7 @@ def _handle_version_params(self, version_params: str) -> None: def _add_text_to_versions(self) -> None: for version in self.return_obj['versions']: version.pop('title') - version['direction'] = self.Direction.ltr.value if version['language'] == 'en' else self.Direction.rtl.value - version.pop('language') + version.pop('language') #should be removed after language is removed from attrs version['text'] = TextRange(self.oref, version['actualLanguage'], version['versionTitle']).text def _add_ref_data(self) -> None: diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 359b7e5365..d012ce47d9 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1341,7 +1341,8 @@ class Version(AbstractTextRecord, abst.AbstractMongoRecord, AbstractSchemaConten "actualLanguage", "isBaseText", 'isSource', - 'isBaseText2' #temp + 'isBaseText2', #temp + 'direction', # 1 for rtl, 2 for ltr ] def __str__(self): From f3e8466d83e1cc5b778b7f0fda651ebe496e2f50 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 20 Jun 2023 13:55:43 +0300 Subject: [PATCH 037/260] fix(Ref): do not try to recognize book in ref initialization in the middle of a word. --- sefaria/model/text.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index d012ce47d9..8659b4750a 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -2870,6 +2870,9 @@ def __init_tref(self): # Remove letter from end of base reference until TitleNode matched, set `title` variable with matched title for l in range(len(base), 0, -1): + if l != len(base) and base[l] not in ' ,.:_': + continue #do not stop in the middle of a word + self.index_node = tndict.get(base[0:l]) if self.index_node: From 243f6cc772f85431abdcfac1c5b29a5e760795b8 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 20 Jun 2023 14:56:46 +0300 Subject: [PATCH 038/260] feat(api texts): return 400 for empty ref. --- api/api_errors.py | 8 -------- api/tests.py | 14 ++++++-------- api/texts_api.py | 6 +----- api/views.py | 2 ++ 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/api/api_errors.py b/api/api_errors.py index c1b7b40af7..3bf38296e2 100644 --- a/api/api_errors.py +++ b/api/api_errors.py @@ -34,11 +34,3 @@ class NoSourceTextError(APIError): def __init__(self, oref: Ref): self.error_code = 103 self.message = f'We do not have the source text for {oref}' - - - -class RefIsEmptyError(APIError): - - def __init__(self, oref: Ref): - self.error_code = 104 - self.message = f'The ref {oref} is empty' diff --git a/api/tests.py b/api/tests.py index 643689df5e..748583c993 100644 --- a/api/tests.py +++ b/api/tests.py @@ -97,6 +97,12 @@ def test_api_get_text_bad_sections(self): data = json.loads(response.content) self.assertEqual(data["error"], "Couldn't understand text sections: 'Job.6-X'.") + def test_api_get_text_empty_ref(self): + response = c.get("/api/v3/texts/Berakhot.1a") + self.assertEqual(400, response.status_code) + data = json.loads(response.content) + self.assertEqual(data["error"], "We have no text for Berakhot 1a.") + def test_api_get_text_no_source(self): response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=en|Brenton's_Septuagint&version=source") self.assertEqual(200, response.status_code) @@ -122,11 +128,3 @@ def test_api_get_text_no_version(self): self.assertEqual(data['errors'][0]['he|Kishkoosh']['error_code'], 101) self.assertEqual(data['errors'][0]['he|Kishkoosh']['message'], 'We do not have version named Kishkoosh with language code he for The Book of Maccabees I 1') - - - def test_api_get_text_empty_ref(self): - response = c.get("/api/v3/texts/Berakhot.1a") - self.assertEqual(200, response.status_code) - data = json.loads(response.content) - self.assertEqual(data['errors'][0]['base']['error_code'], 104) - self.assertEqual(data['errors'][0]['base']['message'], 'The ref Berakhot 1a is empty') diff --git a/api/texts_api.py b/api/texts_api.py index 86aa281c63..f5485a18f2 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -1,9 +1,7 @@ import django django.setup() -from sefaria.model import * from sefaria.datatype.jagged_array import JaggedArray from sefaria.utils.hebrew import hebrew_term -from enum import Enum from .api_errors import * from .helper import split_at_pipe_with_default from typing import List @@ -38,9 +36,7 @@ def __init__(self, oref: Ref, versions_params: List[str]): self.return_obj = {'versions': [], 'errors': []} def _handle_errors(self, lang: str, vtitle: str) -> None: - if self.oref.is_empty(): - error = RefIsEmptyError(self.oref) - elif lang == self.SOURCE: + if lang == self.SOURCE: error = NoSourceTextError(self.oref) elif vtitle and vtitle != self.ALL: error = NoVersionError(self.oref, vtitle, lang) diff --git a/api/views.py b/api/views.py index c7709a4abb..66e66de424 100644 --- a/api/views.py +++ b/api/views.py @@ -10,6 +10,8 @@ def get_texts(request, tref): oref = Ref.instantiate_ref_with_legacy_parse_fallback(tref) except Exception as e: return HttpResponseBadRequest(json.dumps({'error': getattr(e, 'message', str(e))}, ensure_ascii=False)) + if oref.is_empty(): + return HttpResponseBadRequest(json.dumps({'error': f'We have no text for {oref}.'}, ensure_ascii=False)) cb = request.GET.get("callback", None) if request.method == "GET": versions_params = request.GET.getlist('version', []) From 30d7ef1503ac8c4fbbbb48436f8c4c62381703fb Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 10:38:55 +0300 Subject: [PATCH 039/260] refactor(api texts): change names for errors. --- api/api_errors.py | 11 +++++------ api/texts_api.py | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/api/api_errors.py b/api/api_errors.py index 3bf38296e2..65ce8a576e 100644 --- a/api/api_errors.py +++ b/api/api_errors.py @@ -6,30 +6,29 @@ class APIError(): - def __init__(self, error_code=0, message=''): - self.error_code = error_code - self.message = message + def __init__(self): + pass def get_dict(self) -> dict: return {'error_code': self.error_code, 'message': self.message} -class NoVersionError(APIError): +class APINoVersion(APIError): def __init__(self, oref: Ref, vtitle: str, lang: str): self.error_code = 101 self.message = f'We do not have version named {vtitle} with language code {lang} for {oref}' -class NoLanguageVersionError(APIError): +class APINoLanguageVersion(APIError): def __init__(self, oref: Ref, langs: List[str]): self.error_code = 102 self.message = f'We do not have the code language you asked for {oref}. Available codes are {langs}' -class NoSourceTextError(APIError): +class APINoSourceText(APIError): def __init__(self, oref: Ref): self.error_code = 103 diff --git a/api/texts_api.py b/api/texts_api.py index f5485a18f2..47f29b57ce 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -37,12 +37,12 @@ def __init__(self, oref: Ref, versions_params: List[str]): def _handle_errors(self, lang: str, vtitle: str) -> None: if lang == self.SOURCE: - error = NoSourceTextError(self.oref) + error = APINoSourceText(self.oref) elif vtitle and vtitle != self.ALL: - error = NoVersionError(self.oref, vtitle, lang) + error = APINoVersion(self.oref, vtitle, lang) else: availabe_langs = {v['actualLanguage'] for v in self.all_versions} - error = NoLanguageVersionError(self.oref, sorted(availabe_langs)) + error = APINoLanguageVersion(self.oref, sorted(availabe_langs)) self.return_obj['errors'].append({ self.current_params: error.get_dict() }) From 7f9eeef6477444c5effbe1ced9c37c71687c748e Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 10:46:29 +0300 Subject: [PATCH 040/260] docs(api texts): explain why check before append. --- api/texts_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/texts_api.py b/api/texts_api.py index 47f29b57ce..10ac81b08d 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -61,7 +61,7 @@ def _append_required_versions(self, lang: str, vtitle=None) -> None: if vtitle != self.ALL and versions: versions = [max(versions, key=lambda v: v['priority'] or 0)] for version in versions: - if version not in self.return_obj['versions']: + if version not in self.return_obj['versions']: #do not return the same version even if included in two different version params self.return_obj['versions'].append(version) if not versions: self._handle_errors(lang, vtitle) From 3268846cccfb7f1ff4c04d1014f3429283015353 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 10:50:43 +0300 Subject: [PATCH 041/260] refactor(api texts): change names of adding data functions. --- api/texts_api.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index 10ac81b08d..f67e88efa5 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -80,7 +80,7 @@ def _add_text_to_versions(self) -> None: version.pop('language') #should be removed after language is removed from attrs version['text'] = TextRange(self.oref, version['actualLanguage'], version['versionTitle']).text - def _add_ref_data(self) -> None: + def _add_ref_data_to_return_obj(self) -> None: oref = self.oref self.return_obj.update({ 'ref': oref.normal(), @@ -154,7 +154,7 @@ def _reduce_alts_to_ref(self) -> dict: #TODO - copied from TextFamily. if we won return alts_ja.array() - def _add_index_data(self) -> None: + def _add_index_data_to_return_obj(self) -> None: index = self.oref.index self.return_obj.update({ 'indexTitle': index.title, @@ -168,7 +168,7 @@ def _add_index_data(self) -> None: 'alts': self._reduce_alts_to_ref(), }) - def _add_node_data(self) -> None: + def _add_node_data_to_return_obj(self) -> None: inode = self.oref.index_node if getattr(inode, "lengths", None): self.return_obj["lengths"] = getattr(inode, "lengths") @@ -191,7 +191,7 @@ def get_versions_for_query(self) -> dict: self.current_params = version_params self._handle_version_params(version_params) self._add_text_to_versions() - self._add_ref_data() - self._add_index_data() - self._add_node_data() + self._add_ref_data_to_return_obj() + self._add_index_data_to_return_obj() + self._add_node_data_to_return_obj() return self.return_obj From a40741fa8515ea025ae417ab5edf4ff4840e7842 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 10:56:40 +0300 Subject: [PATCH 042/260] docs(api texts): status codes of get_texts. --- api/views.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/api/views.py b/api/views.py index 66e66de424..aee1cbd2c7 100644 --- a/api/views.py +++ b/api/views.py @@ -6,6 +6,13 @@ def get_texts(request, tref): + """ + handle text request based on ref and query params + status codes: + 400 - for invalid ref or empty ref (i.e. has no text in any language) + 405 - unsuppored method + 200 - any other case. when requested version doesn't exist the returned object will include the message + """ try: oref = Ref.instantiate_ref_with_legacy_parse_fallback(tref) except Exception as e: @@ -20,4 +27,4 @@ def get_texts(request, tref): handler = APITextsHandler(oref, versions_params) data = handler.get_versions_for_query() return jsonResponse(data, cb) - return jsonResponse({"error": "Unsupported HTTP method."}, cb) + return jsonResponse({"error": "Unsupported HTTP method."}, cbdocs) From f3f2b525e85064cb426bf465d3d1f2365bb50828 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 10:58:13 +0300 Subject: [PATCH 043/260] fix(api texts): status code 405. --- api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/views.py b/api/views.py index aee1cbd2c7..4895d1446f 100644 --- a/api/views.py +++ b/api/views.py @@ -27,4 +27,4 @@ def get_texts(request, tref): handler = APITextsHandler(oref, versions_params) data = handler.get_versions_for_query() return jsonResponse(data, cb) - return jsonResponse({"error": "Unsupported HTTP method."}, cbdocs) + return jsonResponse({"error": "Unsupported HTTP method."}, cb, 405) From 1031eb1bddf7c148433dd9012d8cb49f132e4a16 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 10:59:08 +0300 Subject: [PATCH 044/260] refactor(api texts): use jsonResponse rather than HttpResponseBadRequest. --- api/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/views.py b/api/views.py index 4895d1446f..c9802aebda 100644 --- a/api/views.py +++ b/api/views.py @@ -16,9 +16,9 @@ def get_texts(request, tref): try: oref = Ref.instantiate_ref_with_legacy_parse_fallback(tref) except Exception as e: - return HttpResponseBadRequest(json.dumps({'error': getattr(e, 'message', str(e))}, ensure_ascii=False)) + return jsonResponse({'error': getattr(e, 'message', str(e))}, 400) if oref.is_empty(): - return HttpResponseBadRequest(json.dumps({'error': f'We have no text for {oref}.'}, ensure_ascii=False)) + return jsonResponse({'error': f'We have no text for {oref}.'}, 400) cb = request.GET.get("callback", None) if request.method == "GET": versions_params = request.GET.getlist('version', []) From 227b2217c01d28478ad4d15391ee4b6fa09bb54d Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 11:04:02 +0300 Subject: [PATCH 045/260] docs(Ref): version_list returns nore than what was documented. --- sefaria/model/text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 8659b4750a..c79e941829 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -4605,13 +4605,13 @@ def versionset(self, lang=None): def version_list(self): """ - A list of available text versions titles and languages matching this ref. + A list of available text versions metadata matching this ref. If this ref is book level, decorate with the first available section of content per version. :return list: each list element is an object with keys 'versionTitle' and 'language' """ fields = Version.optional_attrs + Version.required_attrs - fields.remove('chapter') + fields.remove('chapter') # not metadata versions = VersionSet(self.condition_query()) version_list = [] if self.is_book_level(): From 9b96175527714a0185b98da17bff29fb126f9982 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 11:12:04 +0300 Subject: [PATCH 046/260] fix(api texts): passing params to jsonResponse correctly. --- api/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/views.py b/api/views.py index c9802aebda..dc07ea7a91 100644 --- a/api/views.py +++ b/api/views.py @@ -16,9 +16,9 @@ def get_texts(request, tref): try: oref = Ref.instantiate_ref_with_legacy_parse_fallback(tref) except Exception as e: - return jsonResponse({'error': getattr(e, 'message', str(e))}, 400) + return jsonResponse({'error': getattr(e, 'message', str(e))}, status=400) if oref.is_empty(): - return jsonResponse({'error': f'We have no text for {oref}.'}, 400) + return jsonResponse({'error': f'We have no text for {oref}.'}, status=400) cb = request.GET.get("callback", None) if request.method == "GET": versions_params = request.GET.getlist('version', []) @@ -27,4 +27,4 @@ def get_texts(request, tref): handler = APITextsHandler(oref, versions_params) data = handler.get_versions_for_query() return jsonResponse(data, cb) - return jsonResponse({"error": "Unsupported HTTP method."}, cb, 405) + return jsonResponse({"error": "Unsupported HTTP method."}, cb, status=405) From 1b655c1925c6ba2a7ade63802cd324b9479a0e38 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 11:17:37 +0300 Subject: [PATCH 047/260] fix(api texts): split_at_pipe_with_default for more than 1 pipe. --- api/helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/helper.py b/api/helper.py index aa9b781263..ea93aae014 100644 --- a/api/helper.py +++ b/api/helper.py @@ -3,6 +3,6 @@ def split_at_pipe_with_default(string: str, list_length: int, defaults: List[str]) -> List[str]: #length of defaults should be one less than list_length substrings = string.split('|', list_length-1) - while len(substrings) < list_length: - substrings.append(defaults.pop()) + if len(substrings) < list_length: + substrings += defaults[len(substrings)-list_length:] return substrings From 5b2a018ba2e1e3a13c890299faab9ee7057d1fb6 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 11:20:50 +0300 Subject: [PATCH 048/260] docs(api texts): docs for split_at_pipe_with_default. --- api/helper.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/helper.py b/api/helper.py index ea93aae014..29551c806e 100644 --- a/api/helper.py +++ b/api/helper.py @@ -1,7 +1,13 @@ from typing import List def split_at_pipe_with_default(string: str, list_length: int, defaults: List[str]) -> List[str]: - #length of defaults should be one less than list_length + """ + split a string of query params into list of params by pipe. filling the list with defaults when there are not enough + :param string: + :param list_length: the required length of a parameters list + :param defaults: a list of default strings for potentially missing parameters + :return: list of parematers + """ substrings = string.split('|', list_length-1) if len(substrings) < list_length: substrings += defaults[len(substrings)-list_length:] From 76430e22d868903683a9a35ed3cbd9c0e98e81f7 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 11:40:02 +0300 Subject: [PATCH 049/260] refacot(api texts): move the logics that trims alt_structs from TextFamily and new texts_api to Index. --- api/texts_api.py | 55 +--------------------- sefaria/model/text.py | 105 ++++++++++++++++++++---------------------- 2 files changed, 52 insertions(+), 108 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index f67e88efa5..9417e26c11 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -101,59 +101,6 @@ def _add_ref_data_to_return_obj(self) -> None: 'type': oref.primary_category, #same as primary category }) - def _reduce_alts_to_ref(self) -> dict: #TODO - copied from TextFamily. if we won't remove it, we should find some place for that - """ - this function takes the index's alt_structs and reduce it to the relevant ref - it is necessary for the client side - """ - oref = self.oref - # Set up empty Array that mirrors text structure - alts_ja = JaggedArray() - for key, struct in oref.index.get_alt_structures().items(): - # Assuming these are in order, continue if it is before ours, break if we see one after - for n in struct.get_leaf_nodes(): - wholeRef = Ref(n.wholeRef).default_child_ref().as_ranged_segment_ref() - if wholeRef.ending_ref().precedes(oref): - continue - if wholeRef.starting_ref().follows(oref): - break - - # It's in our territory - wholeRefStart = wholeRef.starting_ref() - if oref.contains(wholeRefStart) and not wholeRefStart.contains(oref): - indxs = [k - 1 for k in wholeRefStart.in_terms_of(oref)] - val = {"en": [], "he": []} - try: - val = alts_ja.get_element(indxs) or val - except IndexError: - pass - val["en"] += [n.primary_title("en")] - val["he"] += [n.primary_title("he")] - val["whole"] = True - alts_ja.set_element(indxs, val) - - if getattr(n, "refs", None): - for i, r in enumerate(n.refs): - # hack to skip Rishon, skip empty refs - if i == 0 or not r: - continue - subRef = Ref(r) - subRefStart = subRef.starting_ref() - if oref.contains(subRefStart) and not subRefStart.contains(oref): - indxs = [k - 1 for k in subRefStart.in_terms_of(oref)] - val = {"en": [], "he": []} - try: - val = alts_ja.get_element(indxs) or val - except IndexError: - pass - val["en"] += [n.sectionString([i + 1], "en", title=False)] - val["he"] += [n.sectionString([i + 1], "he", title=False)] - alts_ja.set_element(indxs, val) - elif subRefStart.follows(oref): - break - - return alts_ja.array() - def _add_index_data_to_return_obj(self) -> None: index = self.oref.index self.return_obj.update({ @@ -165,7 +112,7 @@ def _add_index_data_to_return_obj(self) -> None: 'order': getattr(index, 'order', ''), 'collectiveTitle': getattr(index, 'collective_title', ''), 'heCollectiveTitle': hebrew_term(getattr(index, 'collective_title', '')), - 'alts': self._reduce_alts_to_ref(), + 'alts': index.get_trimmed_alt_structs_for_ref(self.oref), }) def _add_node_data_to_return_obj(self) -> None: diff --git a/sefaria/model/text.py b/sefaria/model/text.py index c79e941829..058124575d 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -420,6 +420,56 @@ def alt_struct_nodes_helper(node, nodes): alt_struct_nodes_helper(node, nodes) return nodes + def get_trimmed_alt_structs_for_ref(self, oref) -> dict: + """ + this function takes the index's alt_structs and reduce it to the relevant ref + """ + # Set up empty Array that mirrors text structure + alts_ja = JaggedArray() + for key, struct in self.get_alt_structures().items(): + # Assuming these are in order, continue if it is before ours, break if we see one after + for n in struct.get_leaf_nodes(): + wholeRef = Ref(n.wholeRef).default_child_ref().as_ranged_segment_ref() + if wholeRef.ending_ref().precedes(oref): + continue + if wholeRef.starting_ref().follows(oref): + break + + # It's in our territory + wholeRefStart = wholeRef.starting_ref() + if oref.contains(wholeRefStart) and not wholeRefStart.contains(oref): + indxs = [k - 1 for k in wholeRefStart.in_terms_of(oref)] + val = {"en": [], "he": []} + try: + val = alts_ja.get_element(indxs) or val + except IndexError: + pass + val["en"] += [n.primary_title("en")] + val["he"] += [n.primary_title("he")] + val["whole"] = True + alts_ja.set_element(indxs, val) + + if getattr(n, "refs", None): + for i, r in enumerate(n.refs): + # hack to skip Rishon, skip empty refs + if i == 0 or not r: + continue + subRef = Ref(r) + subRefStart = subRef.starting_ref() + if oref.contains(subRefStart) and not subRefStart.contains(oref): + indxs = [k - 1 for k in subRefStart.in_terms_of(oref)] + val = {"en": [], "he": []} + try: + val = alts_ja.get_element(indxs) or val + except IndexError: + pass + val["en"] += [n.sectionString([i + 1], "en", title=False)] + val["he"] += [n.sectionString([i + 1], "he", title=False)] + alts_ja.set_element(indxs, val) + elif subRefStart.follows(oref): + break + + return alts_ja.array() def composition_place(self): from . import place @@ -2479,60 +2529,7 @@ def __init__(self, oref, context=1, commentary=True, version=None, lang=None, ve # Adds decoration for the start of each alt structure reference if alts: - # Set up empty Array that mirrors text structure - alts_ja = JaggedArray() - - for key, struct in oref.index.get_alt_structures().items(): - # Assuming these are in order, continue if it is before ours, break if we see one after - for n in struct.get_leaf_nodes(): - wholeRef = Ref(n.wholeRef).default_child_ref().as_ranged_segment_ref() - if wholeRef.ending_ref().precedes(oref): - continue - if wholeRef.starting_ref().follows(oref): - break - - #It's in our territory - wholeRefStart = wholeRef.starting_ref() - if oref.contains(wholeRefStart) and not wholeRefStart.contains(oref): - indxs = [k - 1 for k in wholeRefStart.in_terms_of(oref)] - val = {"en":[], "he":[]} - - try: - val = alts_ja.get_element(indxs) or val - except IndexError: - pass - - val["en"] += [n.primary_title("en")] - val["he"] += [n.primary_title("he")] - val["whole"] = True - - alts_ja.set_element(indxs, val) - - if getattr(n, "refs", None): - for i, r in enumerate(n.refs): - # hack to skip Rishon, skip empty refs - if i == 0 or not r: - continue - subRef = Ref(r) - subRefStart = subRef.starting_ref() - if oref.contains(subRefStart) and not subRefStart.contains(oref): - indxs = [k - 1 for k in subRefStart.in_terms_of(oref)] - val = {"en":[], "he":[]} - - try: - val = alts_ja.get_element(indxs) or val - except IndexError: - pass - - val["en"] += [n.sectionString([i + 1], "en", title=False)] - val["he"] += [n.sectionString([i + 1], "he", title=False)] - - alts_ja.set_element(indxs, val) - - elif subRefStart.follows(oref): - break - - self._alts = alts_ja.array() + self._alts = oref.index.get_trimmed_alt_structs_for_ref(oref) if self._inode.is_virtual: self._index_offsets_by_depth = None else: From ffb86466a02af28f6f594cf9337590a935e4f1ce Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 21 Jun 2023 11:55:33 +0300 Subject: [PATCH 050/260] chore(api texts): tests for split_at_pipe_with_default. --- api/tests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/api/tests.py b/api/tests.py index 748583c993..089329ec99 100644 --- a/api/tests.py +++ b/api/tests.py @@ -3,9 +3,26 @@ django.setup() from reader.tests import SefariaTestCase import json +from api.helper import split_at_pipe_with_default + + +class HelperTests(SefariaTestCase): + def test_split_at_pipe_with_default(self): + for string, list_length, default, expected in [ + ('he|foo bar', 2, [], ['he', 'foo bar']), + ('he|foo bar', 2, ['baz'], ['he', 'foo bar']), + ('he', 2, ['baz'], ['he', 'baz']), + ('he|foo bar|baz', 3, [], ['he', 'foo bar', 'baz']), + ('he|foo bar|baz', 3, ['blue'], ['he', 'foo bar', 'baz']), + ('he|foo bar', 3, ['baz'], ['he', 'foo bar', 'baz']), + ('he', 3, ['foo', 'baz'], ['he', 'foo', 'baz']), + ]: + self.assertEqual(expected, split_at_pipe_with_default(string, list_length, default)) + c = Client() + class APITextsTests(SefariaTestCase): def test_api_get_text_default(self): From bb35e34fdad27eaf466082b91f1b05da1824cb60 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 18 Jul 2023 12:18:08 +0300 Subject: [PATCH 051/260] refactor(api texts): change names in split_at_pipe_with_default --- api/helper.py | 12 ++++++------ api/tests.py | 4 ++-- api/texts_api.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/helper.py b/api/helper.py index 29551c806e..070e39e81a 100644 --- a/api/helper.py +++ b/api/helper.py @@ -1,14 +1,14 @@ from typing import List -def split_at_pipe_with_default(string: str, list_length: int, defaults: List[str]) -> List[str]: +def split_query_param_and_add_defaults(query_string: str, list_length: int, defaults: List[str]) -> List[str]: """ split a string of query params into list of params by pipe. filling the list with defaults when there are not enough - :param string: + :param query_string: :param list_length: the required length of a parameters list :param defaults: a list of default strings for potentially missing parameters :return: list of parematers """ - substrings = string.split('|', list_length-1) - if len(substrings) < list_length: - substrings += defaults[len(substrings)-list_length:] - return substrings + params = query_string.split('|', list_length - 1) + if len(params) < list_length: + params += defaults[len(params)-list_length:] + return params diff --git a/api/tests.py b/api/tests.py index 089329ec99..b04d269ac6 100644 --- a/api/tests.py +++ b/api/tests.py @@ -3,7 +3,7 @@ django.setup() from reader.tests import SefariaTestCase import json -from api.helper import split_at_pipe_with_default +from api.helper import split_query_param_and_add_defaults class HelperTests(SefariaTestCase): @@ -17,7 +17,7 @@ def test_split_at_pipe_with_default(self): ('he|foo bar', 3, ['baz'], ['he', 'foo bar', 'baz']), ('he', 3, ['foo', 'baz'], ['he', 'foo', 'baz']), ]: - self.assertEqual(expected, split_at_pipe_with_default(string, list_length, default)) + self.assertEqual(expected, split_query_param_and_add_defaults(string, list_length, default)) c = Client() diff --git a/api/texts_api.py b/api/texts_api.py index 9417e26c11..890d0da3e6 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -3,7 +3,7 @@ from sefaria.datatype.jagged_array import JaggedArray from sefaria.utils.hebrew import hebrew_term from .api_errors import * -from .helper import split_at_pipe_with_default +from .helper import split_query_param_and_add_defaults from typing import List class APITextsHandler(): @@ -69,7 +69,7 @@ def _append_required_versions(self, lang: str, vtitle=None) -> None: def _handle_version_params(self, version_params: str) -> None: if version_params in self.handled_version_params: return - lang, vtitle = split_at_pipe_with_default(version_params, 2, ['']) + lang, vtitle = split_query_param_and_add_defaults(version_params, 2, ['']) vtitle = vtitle.replace('_', ' ') self._append_required_versions(lang, vtitle) self.handled_version_params.append(version_params) From 95d3d8773a98582e54f1ead2987067e316c3a3b5 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 23 Jul 2023 09:36:25 +0300 Subject: [PATCH 052/260] refactor(api texts): change api_v3 to bi_directional_translation_views. --- sefaria/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sefaria/urls.py b/sefaria/urls.py index 02741bc089..85a3cbf398 100644 --- a/sefaria/urls.py +++ b/sefaria/urls.py @@ -13,7 +13,7 @@ import sourcesheets.views as sheets_views import sefaria.gauth.views as gauth_views import django.contrib.auth.views as django_auth_views -import api.views as v3_views +import api.views as bi_directional_translation_views from sefaria.site.urls import site_urlpatterns @@ -147,7 +147,7 @@ url(r'^api/texts/modify-bulk/(?P<title>.+)$', reader_views.modify_bulk_text_api), url(r'^api/texts/(?P<tref>.+)/(?P<lang>\w\w)/(?P<version>.+)$', reader_views.old_text_versions_api_redirect), url(r'^api/texts/(?P<tref>.+)$', reader_views.texts_api), - url(r'^api/v3/texts/(?P<tref>.+)$', v3_views.get_texts), + url(r'^api/v3/texts/(?P<tref>.+)$', bi_directional_translation_views.get_texts), url(r'^api/index/?$', reader_views.table_of_contents_api), url(r'^api/opensearch-suggestions/?$', reader_views.opensearch_suggestions_api), url(r'^api/index/titles/?$', reader_views.text_titles_api), From aa3dfc6c9df1d9f7767a3686a36c7146a64e4adb Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 25 Jul 2023 11:46:02 +0300 Subject: [PATCH 053/260] refactor(api texts): a new class for handling versions params. --- api/texts_api.py | 66 +++++++++++++++++++++++++++++++++--------------- api/views.py | 7 +++-- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index 890d0da3e6..5e149ffc15 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -1,22 +1,54 @@ import django django.setup() -from sefaria.datatype.jagged_array import JaggedArray from sefaria.utils.hebrew import hebrew_term from .api_errors import * from .helper import split_query_param_and_add_defaults from typing import List -class APITextsHandler(): + +class VersionsParams(): """ - process api calls for text - an api call contains ref and list of version_params - a version params is string divided by pipe as 'lang|vtitle' + an object for managing the versions params for TextsHandler + params can come from an API request or internal (sever side rendering) lang is our code language. special values are: source - for versions in the source language of the text base - as source but with falling to the 'nearest' to source, or what we have defined as such vtitle is the exact versionTitle. special values are: no vtitle - the version with the max priority attr of the specified language all - all versions of the specified language + representing_string is the original string that came from an API call + """ + + def __init__(self, lang: str, vtitle: str, representing_string=''): + self.lang = lang + self.vtitle = vtitle + self.representing_string = representing_string + if not self.representing_string: + self.representing_string = f'{self.lang}|{self.representing_string}' + + def __eq__(self, other): + return isinstance(other, VersionsParams) and self.lang == other.lang and self.vtitle == other.vtitle + + @staticmethod + def parse_api_params(version_params): + """ + an api call contains ref and list of version_params + a version params is string divided by pipe as 'lang|vtitle' + this function takes the list of version_params and returns list of VersionsParams + """ + version_params_list = [] + for params_string in version_params: + lang, vtitle = split_query_param_and_add_defaults(params_string, 2, ['']) + vtitle = vtitle.replace('_', ' ') + version_params = VersionsParams(lang, vtitle, params_string) + if version_params not in version_params_list: + version_params_list.append(version_params) + return version_params_list + + +class TextsForClientHandler(): + """ + process api calls for text return_obj is dict that includes in its root: ref and index data 'versions' - list of versions details and text @@ -27,15 +59,15 @@ class APITextsHandler(): BASE = 'base' SOURCE = 'source' - def __init__(self, oref: Ref, versions_params: List[str]): + def __init__(self, oref: Ref, versions_params: List[VersionsParams]): self.versions_params = versions_params - self.current_params = '' self.oref = oref self.handled_version_params = [] self.all_versions = self.oref.version_list() self.return_obj = {'versions': [], 'errors': []} - def _handle_errors(self, lang: str, vtitle: str) -> None: + def _handle_errors(self, version_params: VersionsParams) -> None: + lang, vtitle = version_params.lang, version_params.vtitle if lang == self.SOURCE: error = APINoSourceText(self.oref) elif vtitle and vtitle != self.ALL: @@ -44,10 +76,11 @@ def _handle_errors(self, lang: str, vtitle: str) -> None: availabe_langs = {v['actualLanguage'] for v in self.all_versions} error = APINoLanguageVersion(self.oref, sorted(availabe_langs)) self.return_obj['errors'].append({ - self.current_params: error.get_dict() + version_params.representing_string: error.get_dict() }) - def _append_required_versions(self, lang: str, vtitle=None) -> None: + def _append_required_versions(self, version_params: VersionsParams) -> None: + lang, vtitle = version_params.lang, version_params.vtitle if lang == self.BASE: lang_condition = lambda v: v['isBaseText2'] #temporal name elif lang == self.SOURCE: @@ -64,15 +97,7 @@ def _append_required_versions(self, lang: str, vtitle=None) -> None: if version not in self.return_obj['versions']: #do not return the same version even if included in two different version params self.return_obj['versions'].append(version) if not versions: - self._handle_errors(lang, vtitle) - - def _handle_version_params(self, version_params: str) -> None: - if version_params in self.handled_version_params: - return - lang, vtitle = split_query_param_and_add_defaults(version_params, 2, ['']) - vtitle = vtitle.replace('_', ' ') - self._append_required_versions(lang, vtitle) - self.handled_version_params.append(version_params) + self._handle_errors(version_params) def _add_text_to_versions(self) -> None: for version in self.return_obj['versions']: @@ -135,8 +160,7 @@ def _add_node_data_to_return_obj(self) -> None: def get_versions_for_query(self) -> dict: for version_params in self.versions_params: - self.current_params = version_params - self._handle_version_params(version_params) + self._append_required_versions(version_params) self._add_text_to_versions() self._add_ref_data_to_return_obj() self._add_index_data_to_return_obj() diff --git a/api/views.py b/api/views.py index dc07ea7a91..fa12e610d5 100644 --- a/api/views.py +++ b/api/views.py @@ -1,7 +1,5 @@ -import json -from django.http import HttpResponseBadRequest from sefaria.model import * -from .texts_api import APITextsHandler +from .texts_api import TextsForClientHandler, VersionsParams from sefaria.client.util import jsonResponse @@ -24,7 +22,8 @@ def get_texts(request, tref): versions_params = request.GET.getlist('version', []) if not versions_params: versions_params = ['base'] - handler = APITextsHandler(oref, versions_params) + versions_params = VersionsParams.parse_api_params(versions_params) + handler = TextsForClientHandler(oref, versions_params) data = handler.get_versions_for_query() return jsonResponse(data, cb) return jsonResponse({"error": "Unsupported HTTP method."}, cb, status=405) From ed09ab74d4f89eae47f5abc92e14e94b3e074c62 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 25 Jul 2023 12:18:32 +0300 Subject: [PATCH 054/260] feat(api texts): option for getting version by versionTitle only (without lang). --- api/texts_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/texts_api.py b/api/texts_api.py index 5e149ffc15..d7ce9db8f0 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -85,8 +85,10 @@ def _append_required_versions(self, version_params: VersionsParams) -> None: lang_condition = lambda v: v['isBaseText2'] #temporal name elif lang == self.SOURCE: lang_condition = lambda v: v['isSource'] - else: + elif lang: lang_condition = lambda v: v['actualLanguage'] == lang + else: + lang_condition = lambda v: True if vtitle and vtitle != self.ALL: versions = [v for v in self.all_versions if lang_condition(v) and v['versionTitle'] == vtitle] else: From 8474203de3cef38714f53e12c78e06fb21e0b444 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 2 Aug 2023 09:09:37 +0300 Subject: [PATCH 055/260] fix(text api): move condition on next ref to next ref. --- api/texts_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index d7ce9db8f0..43b92008b3 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -119,9 +119,9 @@ def _add_ref_data_to_return_obj(self) -> None: 'firstAvailableSectionRef': oref.first_available_section_ref().normal(), 'isSpanning': oref.is_spanning(), 'spanningRefs': [r.normal() for r in oref.split_spanning_ref()], - 'next': oref.next_section_ref().normal(), + 'next': oref.next_section_ref().normal() if oref.next_section_ref() else None, 'prev': oref.prev_section_ref().normal() if oref.prev_section_ref() else None, - 'title': oref.context_ref().normal() if oref.next_section_ref() else None, + 'title': oref.context_ref().normal(), 'book': oref.book, 'heTitle': oref.context_ref().he_normal(), 'primary_category': oref.primary_category, From 76ff1a0cb35bda54e5496451ac9565dfa8f1c3e5 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 21 Aug 2023 11:34:25 +0300 Subject: [PATCH 056/260] refactor(api errors): rename A|PIError to APIDataError. --- api/api_errors.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/api_errors.py b/api/api_errors.py index 65ce8a576e..a1c7a8260f 100644 --- a/api/api_errors.py +++ b/api/api_errors.py @@ -4,7 +4,7 @@ from typing import List -class APIError(): +class APIDataError(): def __init__(self): pass @@ -14,21 +14,21 @@ def get_dict(self) -> dict: 'message': self.message} -class APINoVersion(APIError): +class APINoVersion(APIDataError): def __init__(self, oref: Ref, vtitle: str, lang: str): self.error_code = 101 self.message = f'We do not have version named {vtitle} with language code {lang} for {oref}' -class APINoLanguageVersion(APIError): +class APINoLanguageVersion(APIDataError): def __init__(self, oref: Ref, langs: List[str]): self.error_code = 102 self.message = f'We do not have the code language you asked for {oref}. Available codes are {langs}' -class APINoSourceText(APIError): +class APINoSourceText(APIDataError): def __init__(self, oref: Ref): self.error_code = 103 From f9492e0de0fa39fcacc2b298e19db0f75ffb47be Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 21 Aug 2023 11:40:30 +0300 Subject: [PATCH 057/260] refactor(api errors): add interim class TextsAPIResponseMessage. --- api/api_errors.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/api/api_errors.py b/api/api_errors.py index a1c7a8260f..3ff1dbedb5 100644 --- a/api/api_errors.py +++ b/api/api_errors.py @@ -3,32 +3,39 @@ from sefaria.model import * from typing import List +""" +classes for data errors in API calls. +used when part of the data that was requested exists and returned, and part is missing. +""" class APIDataError(): def __init__(self): pass + +class TextsAPIResponseMessage(APIDataError): + def get_dict(self) -> dict: return {'error_code': self.error_code, 'message': self.message} -class APINoVersion(APIDataError): +class APINoVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, vtitle: str, lang: str): self.error_code = 101 self.message = f'We do not have version named {vtitle} with language code {lang} for {oref}' -class APINoLanguageVersion(APIDataError): +class APINoLanguageVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, langs: List[str]): self.error_code = 102 self.message = f'We do not have the code language you asked for {oref}. Available codes are {langs}' -class APINoSourceText(APIDataError): +class APINoSourceText(TextsAPIResponseMessage): def __init__(self, oref: Ref): self.error_code = 103 From 830143094359632c7bb7f3a975f03a21e27c851e Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 21 Aug 2023 11:43:09 +0300 Subject: [PATCH 058/260] docs(api errors): doc strings for the parent classes. --- api/api_errors.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/api_errors.py b/api/api_errors.py index 3ff1dbedb5..972c5a1ad8 100644 --- a/api/api_errors.py +++ b/api/api_errors.py @@ -9,12 +9,18 @@ """ class APIDataError(): + """ + general class + """ def __init__(self): pass class TextsAPIResponseMessage(APIDataError): + """ + class for returning a message and an error code + """ def get_dict(self) -> dict: return {'error_code': self.error_code, From 260902879e597ec9349811900eb7eb28a39fb4e9 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 21 Aug 2023 11:52:07 +0300 Subject: [PATCH 059/260] refactor(api errors): rename get_dict to get_message. --- api/api_errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api_errors.py b/api/api_errors.py index 972c5a1ad8..dd170d8162 100644 --- a/api/api_errors.py +++ b/api/api_errors.py @@ -22,7 +22,7 @@ class TextsAPIResponseMessage(APIDataError): class for returning a message and an error code """ - def get_dict(self) -> dict: + def get_message(self) -> dict: return {'error_code': self.error_code, 'message': self.message} From 67e86a5ce08cad3c2054b27f6a54bd20bdd04586 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 21 Aug 2023 11:53:10 +0300 Subject: [PATCH 060/260] fix(api errors): rename get_dict to get_message when called. --- api/texts_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/texts_api.py b/api/texts_api.py index 43b92008b3..81d430aaf6 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -76,7 +76,7 @@ def _handle_errors(self, version_params: VersionsParams) -> None: availabe_langs = {v['actualLanguage'] for v in self.all_versions} error = APINoLanguageVersion(self.oref, sorted(availabe_langs)) self.return_obj['errors'].append({ - version_params.representing_string: error.get_dict() + version_params.representing_string: error.get_message() }) def _append_required_versions(self, version_params: VersionsParams) -> None: From 7c6404702401ba9821c26f2d9ca8785cf96a4313 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 21 Aug 2023 13:57:42 +0300 Subject: [PATCH 061/260] refactor(api tests): unit test without inheriting SefariaTestCase. --- api/tests.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/api/tests.py b/api/tests.py index b04d269ac6..b4dfbc787c 100644 --- a/api/tests.py +++ b/api/tests.py @@ -6,18 +6,17 @@ from api.helper import split_query_param_and_add_defaults -class HelperTests(SefariaTestCase): - def test_split_at_pipe_with_default(self): - for string, list_length, default, expected in [ - ('he|foo bar', 2, [], ['he', 'foo bar']), - ('he|foo bar', 2, ['baz'], ['he', 'foo bar']), - ('he', 2, ['baz'], ['he', 'baz']), - ('he|foo bar|baz', 3, [], ['he', 'foo bar', 'baz']), - ('he|foo bar|baz', 3, ['blue'], ['he', 'foo bar', 'baz']), - ('he|foo bar', 3, ['baz'], ['he', 'foo bar', 'baz']), - ('he', 3, ['foo', 'baz'], ['he', 'foo', 'baz']), - ]: - self.assertEqual(expected, split_query_param_and_add_defaults(string, list_length, default)) +def test_split_at_pipe_with_default(): + for string, list_length, default, expected in [ + ('he|foo bar', 2, [], ['he', 'foo bar']), + ('he|foo bar', 2, ['baz'], ['he', 'foo bar']), + ('he', 2, ['baz'], ['he', 'baz']), + ('he|foo bar|baz', 3, [], ['he', 'foo bar', 'baz']), + ('he|foo bar|baz', 3, ['blue'], ['he', 'foo bar', 'baz']), + ('he|foo bar', 3, ['baz'], ['he', 'foo bar', 'baz']), + ('he', 3, ['foo', 'baz'], ['he', 'foo', 'baz']), + ]: + assert expected == split_query_param_and_add_defaults(string, list_length, default) c = Client() From 719525b313cb2dfed4026243e72766f58ce4acaa Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Mon, 21 Aug 2023 15:22:00 +0300 Subject: [PATCH 062/260] refactor(api tests): create a representing_string when needed rather than in init. --- api/texts_api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index 81d430aaf6..5e3899fcfa 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -23,8 +23,6 @@ def __init__(self, lang: str, vtitle: str, representing_string=''): self.lang = lang self.vtitle = vtitle self.representing_string = representing_string - if not self.representing_string: - self.representing_string = f'{self.lang}|{self.representing_string}' def __eq__(self, other): return isinstance(other, VersionsParams) and self.lang == other.lang and self.vtitle == other.vtitle @@ -75,6 +73,7 @@ def _handle_errors(self, version_params: VersionsParams) -> None: else: availabe_langs = {v['actualLanguage'] for v in self.all_versions} error = APINoLanguageVersion(self.oref, sorted(availabe_langs)) + representing_string = version_params.representing_string or f'{version_params.lang}|{version_params.representing_string}' self.return_obj['errors'].append({ version_params.representing_string: error.get_message() }) From af4a9810f76d4f8f89710f9e2f52531cf1bac2db Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 22 Aug 2023 11:26:05 +0300 Subject: [PATCH 063/260] refactor(api tests): use enums for error codes. --- api/api_errors.py | 12 +++++++++--- api/tests.py | 7 ++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/api/api_errors.py b/api/api_errors.py index dd170d8162..a7ed763b6b 100644 --- a/api/api_errors.py +++ b/api/api_errors.py @@ -2,6 +2,12 @@ django.setup() from sefaria.model import * from typing import List +from enum import Enum + +class APIWarningCode(Enum): + APINoVersion = 101 + APINoLanguageVersion = 102 + APINoSourceText = 103 """ classes for data errors in API calls. @@ -30,19 +36,19 @@ def get_message(self) -> dict: class APINoVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, vtitle: str, lang: str): - self.error_code = 101 + self.error_code = APIWarningCode.APINoVersion.value self.message = f'We do not have version named {vtitle} with language code {lang} for {oref}' class APINoLanguageVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, langs: List[str]): - self.error_code = 102 + self.error_code = APIWarningCode.APINoLanguageVersion.value self.message = f'We do not have the code language you asked for {oref}. Available codes are {langs}' class APINoSourceText(TextsAPIResponseMessage): def __init__(self, oref: Ref): - self.error_code = 103 + self.error_code = APIWarningCode.APINoSourceText.value self.message = f'We do not have the source text for {oref}' diff --git a/api/tests.py b/api/tests.py index b4dfbc787c..9887e7017d 100644 --- a/api/tests.py +++ b/api/tests.py @@ -4,6 +4,7 @@ from reader.tests import SefariaTestCase import json from api.helper import split_query_param_and_add_defaults +from api.api_errors import APIWarningCode def test_split_at_pipe_with_default(): @@ -124,7 +125,7 @@ def test_api_get_text_no_source(self): self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) - self.assertEqual(data['errors'][0]['source']['error_code'], 103) + self.assertEqual(data['errors'][0]['source']['error_code'], APIWarningCode.APINoSourceText.value) self.assertEqual(data['errors'][0]['source']['message'], 'We do not have the source text for The Book of Maccabees I 1') def test_api_get_text_no_language(self): @@ -132,7 +133,7 @@ def test_api_get_text_no_language(self): self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) - self.assertEqual(data['errors'][0]['sgrg|all']['error_code'], 102) + self.assertEqual(data['errors'][0]['sgrg|all']['error_code'], APIWarningCode.APINoLanguageVersion.value) self.assertEqual(data['errors'][0]['sgrg|all']['message'], "We do not have the code language you asked for The Book of Maccabees I 1. Available codes are ['en', 'he']") @@ -141,6 +142,6 @@ def test_api_get_text_no_version(self): self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) - self.assertEqual(data['errors'][0]['he|Kishkoosh']['error_code'], 101) + self.assertEqual(data['errors'][0]['he|Kishkoosh']['error_code'], APIWarningCode.APINoVersion.value) self.assertEqual(data['errors'][0]['he|Kishkoosh']['message'], 'We do not have version named Kishkoosh with language code he for The Book of Maccabees I 1') From 9e0739f9802bd8ec15aec44b3a90cbe49667f6c0 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 22 Aug 2023 11:33:20 +0300 Subject: [PATCH 064/260] refactor(api tests): change errors to warnings. --- api/{api_errors.py => api_warnings.py} | 16 ++++++++-------- api/tests.py | 14 +++++++------- api/texts_api.py | 20 ++++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) rename api/{api_errors.py => api_warnings.py} (71%) diff --git a/api/api_errors.py b/api/api_warnings.py similarity index 71% rename from api/api_errors.py rename to api/api_warnings.py index a7ed763b6b..40cb37b1cc 100644 --- a/api/api_errors.py +++ b/api/api_warnings.py @@ -10,11 +10,11 @@ class APIWarningCode(Enum): APINoSourceText = 103 """ -classes for data errors in API calls. +classes for data warnings in API calls. used when part of the data that was requested exists and returned, and part is missing. """ -class APIDataError(): +class APIDatawarning(): """ general class """ @@ -23,32 +23,32 @@ def __init__(self): pass -class TextsAPIResponseMessage(APIDataError): +class TextsAPIResponseMessage(APIDatawarning): """ - class for returning a message and an error code + class for returning a message and an warning code """ def get_message(self) -> dict: - return {'error_code': self.error_code, + return {'warning_code': self.warning_code, 'message': self.message} class APINoVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, vtitle: str, lang: str): - self.error_code = APIWarningCode.APINoVersion.value + self.warning_code = APIWarningCode.APINoVersion.value self.message = f'We do not have version named {vtitle} with language code {lang} for {oref}' class APINoLanguageVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, langs: List[str]): - self.error_code = APIWarningCode.APINoLanguageVersion.value + self.warning_code = APIWarningCode.APINoLanguageVersion.value self.message = f'We do not have the code language you asked for {oref}. Available codes are {langs}' class APINoSourceText(TextsAPIResponseMessage): def __init__(self, oref: Ref): - self.error_code = APIWarningCode.APINoSourceText.value + self.warning_code = APIWarningCode.APINoSourceText.value self.message = f'We do not have the source text for {oref}' diff --git a/api/tests.py b/api/tests.py index 9887e7017d..4eb6da02cc 100644 --- a/api/tests.py +++ b/api/tests.py @@ -4,7 +4,7 @@ from reader.tests import SefariaTestCase import json from api.helper import split_query_param_and_add_defaults -from api.api_errors import APIWarningCode +from api.api_warnings import APIWarningCode def test_split_at_pipe_with_default(): @@ -125,16 +125,16 @@ def test_api_get_text_no_source(self): self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) - self.assertEqual(data['errors'][0]['source']['error_code'], APIWarningCode.APINoSourceText.value) - self.assertEqual(data['errors'][0]['source']['message'], 'We do not have the source text for The Book of Maccabees I 1') + self.assertEqual(data['warnings'][0]['source']['warning_code'], APIWarningCode.APINoSourceText.value) + self.assertEqual(data['warnings'][0]['source']['message'], 'We do not have the source text for The Book of Maccabees I 1') def test_api_get_text_no_language(self): response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=en|Brenton's_Septuagint&version=sgrg|all") self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) - self.assertEqual(data['errors'][0]['sgrg|all']['error_code'], APIWarningCode.APINoLanguageVersion.value) - self.assertEqual(data['errors'][0]['sgrg|all']['message'], + self.assertEqual(data['warnings'][0]['sgrg|all']['warning_code'], APIWarningCode.APINoLanguageVersion.value) + self.assertEqual(data['warnings'][0]['sgrg|all']['message'], "We do not have the code language you asked for The Book of Maccabees I 1. Available codes are ['en', 'he']") def test_api_get_text_no_version(self): @@ -142,6 +142,6 @@ def test_api_get_text_no_version(self): self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) - self.assertEqual(data['errors'][0]['he|Kishkoosh']['error_code'], APIWarningCode.APINoVersion.value) - self.assertEqual(data['errors'][0]['he|Kishkoosh']['message'], + self.assertEqual(data['warnings'][0]['he|Kishkoosh']['warning_code'], APIWarningCode.APINoVersion.value) + self.assertEqual(data['warnings'][0]['he|Kishkoosh']['message'], 'We do not have version named Kishkoosh with language code he for The Book of Maccabees I 1') diff --git a/api/texts_api.py b/api/texts_api.py index 5e3899fcfa..6fa516decf 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -1,7 +1,7 @@ import django django.setup() from sefaria.utils.hebrew import hebrew_term -from .api_errors import * +from .api_warnings import * from .helper import split_query_param_and_add_defaults from typing import List @@ -50,7 +50,7 @@ class TextsForClientHandler(): return_obj is dict that includes in its root: ref and index data 'versions' - list of versions details and text - 'errors' - for any version_params that had an error + 'warning' - for any version_params that has an warning """ ALL = 'all' @@ -62,20 +62,20 @@ def __init__(self, oref: Ref, versions_params: List[VersionsParams]): self.oref = oref self.handled_version_params = [] self.all_versions = self.oref.version_list() - self.return_obj = {'versions': [], 'errors': []} + self.return_obj = {'versions': [], 'warnings': []} - def _handle_errors(self, version_params: VersionsParams) -> None: + def _handle_warnings(self, version_params: VersionsParams) -> None: lang, vtitle = version_params.lang, version_params.vtitle if lang == self.SOURCE: - error = APINoSourceText(self.oref) + warning = APINoSourceText(self.oref) elif vtitle and vtitle != self.ALL: - error = APINoVersion(self.oref, vtitle, lang) + warning = APINoVersion(self.oref, vtitle, lang) else: availabe_langs = {v['actualLanguage'] for v in self.all_versions} - error = APINoLanguageVersion(self.oref, sorted(availabe_langs)) + warning = APINoLanguageVersion(self.oref, sorted(availabe_langs)) representing_string = version_params.representing_string or f'{version_params.lang}|{version_params.representing_string}' - self.return_obj['errors'].append({ - version_params.representing_string: error.get_message() + self.return_obj['warnings'].append({ + version_params.representing_string: warning.get_message() }) def _append_required_versions(self, version_params: VersionsParams) -> None: @@ -98,7 +98,7 @@ def _append_required_versions(self, version_params: VersionsParams) -> None: if version not in self.return_obj['versions']: #do not return the same version even if included in two different version params self.return_obj['versions'].append(version) if not versions: - self._handle_errors(version_params) + self._handle_warnings(version_params) def _add_text_to_versions(self) -> None: for version in self.return_obj['versions']: From 769f2f619fb29a44ade6f091b8d46dd0d0ac2fc2 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 22 Aug 2023 11:54:26 +0300 Subject: [PATCH 065/260] refactor(api tests): remove callback from jsonResponse calls. --- api/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/views.py b/api/views.py index fa12e610d5..66c0542208 100644 --- a/api/views.py +++ b/api/views.py @@ -17,7 +17,6 @@ def get_texts(request, tref): return jsonResponse({'error': getattr(e, 'message', str(e))}, status=400) if oref.is_empty(): return jsonResponse({'error': f'We have no text for {oref}.'}, status=400) - cb = request.GET.get("callback", None) if request.method == "GET": versions_params = request.GET.getlist('version', []) if not versions_params: @@ -25,5 +24,5 @@ def get_texts(request, tref): versions_params = VersionsParams.parse_api_params(versions_params) handler = TextsForClientHandler(oref, versions_params) data = handler.get_versions_for_query() - return jsonResponse(data, cb) - return jsonResponse({"error": "Unsupported HTTP method."}, cb, status=405) + return jsonResponse(data) + return jsonResponse({"error": "Unsupported HTTP method."}, status=405) From db2f8eee67ee4d2f0646ce0fa7f45a8d35bac40f Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 23 Aug 2023 07:20:12 +0300 Subject: [PATCH 066/260] refactor(api tests): view class rather than function. --- api/texts_api.py | 2 +- api/views.py | 31 ++++++++++++++----------------- sefaria/urls.py | 2 +- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/api/texts_api.py b/api/texts_api.py index 6fa516decf..2ee0588b29 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -75,7 +75,7 @@ def _handle_warnings(self, version_params: VersionsParams) -> None: warning = APINoLanguageVersion(self.oref, sorted(availabe_langs)) representing_string = version_params.representing_string or f'{version_params.lang}|{version_params.representing_string}' self.return_obj['warnings'].append({ - version_params.representing_string: warning.get_message() + representing_string: warning.get_message() }) def _append_required_versions(self, version_params: VersionsParams) -> None: diff --git a/api/views.py b/api/views.py index 66c0542208..fa55f9f615 100644 --- a/api/views.py +++ b/api/views.py @@ -1,28 +1,25 @@ from sefaria.model import * from .texts_api import TextsForClientHandler, VersionsParams from sefaria.client.util import jsonResponse +from django.views import View -def get_texts(request, tref): - """ - handle text request based on ref and query params - status codes: - 400 - for invalid ref or empty ref (i.e. has no text in any language) - 405 - unsuppored method - 200 - any other case. when requested version doesn't exist the returned object will include the message - """ - try: - oref = Ref.instantiate_ref_with_legacy_parse_fallback(tref) - except Exception as e: - return jsonResponse({'error': getattr(e, 'message', str(e))}, status=400) - if oref.is_empty(): - return jsonResponse({'error': f'We have no text for {oref}.'}, status=400) - if request.method == "GET": +class Text(View): + + def dispatch(self, request, *args, **kwargs): + try: + self.oref = Ref.instantiate_ref_with_legacy_parse_fallback(kwargs['tref']) + except Exception as e: + return jsonResponse({'error': getattr(e, 'message', str(e))}, status=400) + return super().dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + if self.oref.is_empty(): + return jsonResponse({'error': f'We have no text for {self.oref}.'}, status=400) versions_params = request.GET.getlist('version', []) if not versions_params: versions_params = ['base'] versions_params = VersionsParams.parse_api_params(versions_params) - handler = TextsForClientHandler(oref, versions_params) + handler = TextsForClientHandler(self.oref, versions_params) data = handler.get_versions_for_query() return jsonResponse(data) - return jsonResponse({"error": "Unsupported HTTP method."}, status=405) diff --git a/sefaria/urls.py b/sefaria/urls.py index ee1c1f9235..f670e3e7ec 100644 --- a/sefaria/urls.py +++ b/sefaria/urls.py @@ -150,7 +150,7 @@ url(r'^api/texts/modify-bulk/(?P<title>.+)$', reader_views.modify_bulk_text_api), url(r'^api/texts/(?P<tref>.+)/(?P<lang>\w\w)/(?P<version>.+)$', reader_views.old_text_versions_api_redirect), url(r'^api/texts/(?P<tref>.+)$', reader_views.texts_api), - url(r'^api/v3/texts/(?P<tref>.+)$', bi_directional_translation_views.get_texts), + url(r'^api/v3/texts/(?P<tref>.+)$', bi_directional_translation_views.Text.as_view()), url(r'^api/index/?$', reader_views.table_of_contents_api), url(r'^api/opensearch-suggestions/?$', reader_views.opensearch_suggestions_api), url(r'^api/index/titles/?$', reader_views.text_titles_api), From 3b7bbf42e0f2003b6e0b4ffe290895414e130d81 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 23 Aug 2023 07:27:21 +0300 Subject: [PATCH 067/260] refactor(api tests): rename as api_views. --- sefaria/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sefaria/urls.py b/sefaria/urls.py index f670e3e7ec..8a760a50af 100644 --- a/sefaria/urls.py +++ b/sefaria/urls.py @@ -14,7 +14,7 @@ import sourcesheets.views as sheets_views import sefaria.gauth.views as gauth_views import django.contrib.auth.views as django_auth_views -import api.views as bi_directional_translation_views +import api.views as api_views from sefaria.site.urls import site_urlpatterns @@ -150,7 +150,7 @@ url(r'^api/texts/modify-bulk/(?P<title>.+)$', reader_views.modify_bulk_text_api), url(r'^api/texts/(?P<tref>.+)/(?P<lang>\w\w)/(?P<version>.+)$', reader_views.old_text_versions_api_redirect), url(r'^api/texts/(?P<tref>.+)$', reader_views.texts_api), - url(r'^api/v3/texts/(?P<tref>.+)$', bi_directional_translation_views.Text.as_view()), + url(r'^api/v3/texts/(?P<tref>.+)$', api_views.Text.as_view()), url(r'^api/index/?$', reader_views.table_of_contents_api), url(r'^api/opensearch-suggestions/?$', reader_views.opensearch_suggestions_api), url(r'^api/index/titles/?$', reader_views.text_titles_api), From 8fc12368354bcd616789792bdeada858cd4265f9 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 23 Aug 2023 14:35:21 +0300 Subject: [PATCH 068/260] refactor(api texts): split api params in view. --- api/helper.py | 14 ------------ api/tests.py | 14 ------------ api/texts_api.py | 55 ++++++------------------------------------------ api/views.py | 13 ++++++++++-- 4 files changed, 18 insertions(+), 78 deletions(-) delete mode 100644 api/helper.py diff --git a/api/helper.py b/api/helper.py deleted file mode 100644 index 070e39e81a..0000000000 --- a/api/helper.py +++ /dev/null @@ -1,14 +0,0 @@ -from typing import List - -def split_query_param_and_add_defaults(query_string: str, list_length: int, defaults: List[str]) -> List[str]: - """ - split a string of query params into list of params by pipe. filling the list with defaults when there are not enough - :param query_string: - :param list_length: the required length of a parameters list - :param defaults: a list of default strings for potentially missing parameters - :return: list of parematers - """ - params = query_string.split('|', list_length - 1) - if len(params) < list_length: - params += defaults[len(params)-list_length:] - return params diff --git a/api/tests.py b/api/tests.py index 4eb6da02cc..05398172a0 100644 --- a/api/tests.py +++ b/api/tests.py @@ -3,23 +3,9 @@ django.setup() from reader.tests import SefariaTestCase import json -from api.helper import split_query_param_and_add_defaults from api.api_warnings import APIWarningCode -def test_split_at_pipe_with_default(): - for string, list_length, default, expected in [ - ('he|foo bar', 2, [], ['he', 'foo bar']), - ('he|foo bar', 2, ['baz'], ['he', 'foo bar']), - ('he', 2, ['baz'], ['he', 'baz']), - ('he|foo bar|baz', 3, [], ['he', 'foo bar', 'baz']), - ('he|foo bar|baz', 3, ['blue'], ['he', 'foo bar', 'baz']), - ('he|foo bar', 3, ['baz'], ['he', 'foo bar', 'baz']), - ('he', 3, ['foo', 'baz'], ['he', 'foo', 'baz']), - ]: - assert expected == split_query_param_and_add_defaults(string, list_length, default) - - c = Client() diff --git a/api/texts_api.py b/api/texts_api.py index 2ee0588b29..3f15d2f02f 100644 --- a/api/texts_api.py +++ b/api/texts_api.py @@ -2,48 +2,9 @@ django.setup() from sefaria.utils.hebrew import hebrew_term from .api_warnings import * -from .helper import split_query_param_and_add_defaults from typing import List -class VersionsParams(): - """ - an object for managing the versions params for TextsHandler - params can come from an API request or internal (sever side rendering) - lang is our code language. special values are: - source - for versions in the source language of the text - base - as source but with falling to the 'nearest' to source, or what we have defined as such - vtitle is the exact versionTitle. special values are: - no vtitle - the version with the max priority attr of the specified language - all - all versions of the specified language - representing_string is the original string that came from an API call - """ - - def __init__(self, lang: str, vtitle: str, representing_string=''): - self.lang = lang - self.vtitle = vtitle - self.representing_string = representing_string - - def __eq__(self, other): - return isinstance(other, VersionsParams) and self.lang == other.lang and self.vtitle == other.vtitle - - @staticmethod - def parse_api_params(version_params): - """ - an api call contains ref and list of version_params - a version params is string divided by pipe as 'lang|vtitle' - this function takes the list of version_params and returns list of VersionsParams - """ - version_params_list = [] - for params_string in version_params: - lang, vtitle = split_query_param_and_add_defaults(params_string, 2, ['']) - vtitle = vtitle.replace('_', ' ') - version_params = VersionsParams(lang, vtitle, params_string) - if version_params not in version_params_list: - version_params_list.append(version_params) - return version_params_list - - class TextsForClientHandler(): """ process api calls for text @@ -57,15 +18,14 @@ class TextsForClientHandler(): BASE = 'base' SOURCE = 'source' - def __init__(self, oref: Ref, versions_params: List[VersionsParams]): + def __init__(self, oref: Ref, versions_params: List[List[str]]): self.versions_params = versions_params self.oref = oref self.handled_version_params = [] self.all_versions = self.oref.version_list() self.return_obj = {'versions': [], 'warnings': []} - def _handle_warnings(self, version_params: VersionsParams) -> None: - lang, vtitle = version_params.lang, version_params.vtitle + def _handle_warnings(self, lang: str, vtitle: str, params_str: str) -> None: if lang == self.SOURCE: warning = APINoSourceText(self.oref) elif vtitle and vtitle != self.ALL: @@ -73,13 +33,12 @@ def _handle_warnings(self, version_params: VersionsParams) -> None: else: availabe_langs = {v['actualLanguage'] for v in self.all_versions} warning = APINoLanguageVersion(self.oref, sorted(availabe_langs)) - representing_string = version_params.representing_string or f'{version_params.lang}|{version_params.representing_string}' + representing_string = params_str or f'{lang}|{vtitle}' self.return_obj['warnings'].append({ representing_string: warning.get_message() }) - def _append_required_versions(self, version_params: VersionsParams) -> None: - lang, vtitle = version_params.lang, version_params.vtitle + def _append_required_versions(self, lang: str, vtitle: str, params_str: str) -> None: if lang == self.BASE: lang_condition = lambda v: v['isBaseText2'] #temporal name elif lang == self.SOURCE: @@ -98,7 +57,7 @@ def _append_required_versions(self, version_params: VersionsParams) -> None: if version not in self.return_obj['versions']: #do not return the same version even if included in two different version params self.return_obj['versions'].append(version) if not versions: - self._handle_warnings(version_params) + self._handle_warnings(lang, vtitle, params_str) def _add_text_to_versions(self) -> None: for version in self.return_obj['versions']: @@ -160,8 +119,8 @@ def _add_node_data_to_return_obj(self) -> None: }) def get_versions_for_query(self) -> dict: - for version_params in self.versions_params: - self._append_required_versions(version_params) + for lang, vtitle, params_str in self.versions_params: + self._append_required_versions(lang, vtitle, params_str) self._add_text_to_versions() self._add_ref_data_to_return_obj() self._add_index_data_to_return_obj() diff --git a/api/views.py b/api/views.py index fa55f9f615..9678ba210a 100644 --- a/api/views.py +++ b/api/views.py @@ -1,7 +1,8 @@ from sefaria.model import * -from .texts_api import TextsForClientHandler, VersionsParams +from .texts_api import TextsForClientHandler from sefaria.client.util import jsonResponse from django.views import View +from typing import List class Text(View): @@ -13,13 +14,21 @@ def dispatch(self, request, *args, **kwargs): return jsonResponse({'error': getattr(e, 'message', str(e))}, status=400) return super().dispatch(request, *args, **kwargs) + @staticmethod + def split_piped_params(params_string) -> List[str]: + params = params_string.split('|') + if len(params) < 2: + params.append('') + params[1] = params[1].replace('_', ' ') + return params + def get(self, request, *args, **kwargs): if self.oref.is_empty(): return jsonResponse({'error': f'We have no text for {self.oref}.'}, status=400) versions_params = request.GET.getlist('version', []) if not versions_params: versions_params = ['base'] - versions_params = VersionsParams.parse_api_params(versions_params) + versions_params = [self.split_piped_params(param_str) + [param_str] for param_str in versions_params] handler = TextsForClientHandler(self.oref, versions_params) data = handler.get_versions_for_query() return jsonResponse(data) From 5929826633926599a92d1f174a808ec0d13d560d Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 23 Aug 2023 14:57:45 +0300 Subject: [PATCH 069/260] refactor(api texts): restore TextChunk. --- sefaria/model/text.py | 443 ++++++++++++++++++------------------------ 1 file changed, 194 insertions(+), 249 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index b8cd392de7..8ca31587f5 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1738,12 +1738,28 @@ def __call__(cls, *args, **kwargs): else: return super(TextFamilyDelegator, cls).__call__(*args, **kwargs) -class TextRange(AbstractTextRecord, metaclass=TextFamilyDelegator): + +class TextChunk(AbstractTextRecord, metaclass=TextFamilyDelegator): + """ + A chunk of text corresponding to the provided :class:`Ref`, language, and optional version name. + If it is possible to get a more complete text by merging multiple versions, a merged result will be returned. + + :param oref: :class:`Ref` + :param lang: "he" or "en". "he" means all rtl languages and "en" means all ltr languages + :param vtitle: optional. Title of the version desired. + :param actual_lang: optional. if vtitle isn't specified, prefer to find a version with ISO language `actual_lang`. As opposed to `lang` which can only be "he" or "en", `actual_lang` can be any valid 2 letter ISO language code. + """ text_attr = "text" - def __init__(self, oref, actual_lang='en', vtitle=None, **kwargs): - #kwargs are only for supporting old TextChunck. should be removed after merging + def __init__(self, oref, lang="en", vtitle=None, exclude_copyrighted=False, actual_lang=None, fallback_on_default_version=False): + """ + :param oref: + :type oref: Ref + :param lang: "he" or "en" + :param vtitle: + :return: + """ if isinstance(oref.index_node, JaggedArrayNode): self._oref = oref else: @@ -1752,41 +1768,57 @@ def __init__(self, oref, actual_lang='en', vtitle=None, **kwargs): raise InputError("Can not get TextChunk at this level, please provide a more precise reference") self._oref = child_ref self._ref_depth = len(self._oref.sections) - self.actual_lang = actual_lang self._versions = [] self._version_ids = None - self._saveable = False + self._saveable = False # Can this TextChunk be saved? + + self.lang = lang self.is_merged = False self.sources = [] self.text = self._original_text = self.empty_text() self.vtitle = vtitle + self.full_version = None self.versionSource = None # handling of source is hacky - self._choose_version(actual_lang=actual_lang, **kwargs) #kactual_lang and wargs are only for supporting old TextChunck. should be removed after merging - def _choose_version(self, **kwargs): #kwargs are only for supporting old TextChunck. should be removed after merging - if self.actual_lang and self.vtitle: + if lang and vtitle and not fallback_on_default_version: self._saveable = True - v = Version().load({"title": self._oref.index.title, "actualLanguage": self.actual_lang, "versionTitle": self.vtitle}, self._oref.part_projection()) + v = Version().load({"title": self._oref.index.title, "language": lang, "versionTitle": vtitle}, self._oref.part_projection()) + if exclude_copyrighted and v.is_copyrighted(): + raise InputError("Can not provision copyrighted text. {} ({}/{})".format(oref.normal(), vtitle, lang)) if v: self._versions += [v] - self.text = self._original_text = self.trim_text(v.content_node(self._oref.index_node)) - elif self.actual_lang: - self._choose_version_by_lang() + try: + self.text = self._original_text = self.trim_text(v.content_node(self._oref.index_node)) + except TypeError: + raise MissingKeyError(f'The version {vtitle} exists but has no key for the node {self._oref.index_node}') + elif lang: + if actual_lang is not None: + self._choose_version_by_lang(oref, lang, exclude_copyrighted, actual_lang, prioritized_vtitle=vtitle) + else: + self._choose_version_by_lang(oref, lang, exclude_copyrighted, prioritized_vtitle=vtitle) else: raise Exception("TextChunk requires a language.") - if not self._versions: - version_title_message = f" and version title '{self.vtile}'" if self.vtile else '' - raise NoVersionFoundError(f"No text record found for '{self._oref.index.title}' in {self.actual_lang}{version_title_message}") - def _choose_version_by_lang(self) -> None: - vset = VersionSet(self._oref.condition_query(actual_lang=self.actual_lang), proj=self._oref.part_projection()) + def _choose_version_by_lang(self, oref, lang: str, exclude_copyrighted: bool, actual_lang: str = None, prioritized_vtitle: str = None) -> None: + if prioritized_vtitle: + actual_lang = None + vset = VersionSet(self._oref.condition_query(lang, actual_lang), proj=self._oref.part_projection()) + if len(vset) == 0: + if VersionSet({"title": self._oref.index.title}).count() == 0: + raise NoVersionFoundError("No text record found for '{}'".format(self._oref.index.title)) + return if len(vset) == 1: v = vset[0] + if exclude_copyrighted and v.is_copyrighted(): + raise InputError("Can not provision copyrighted text. {} ({}/{})".format(oref.normal(), v.versionTitle, v.language)) self._versions += [v] self.text = self.trim_text(v.content_node(self._oref.index_node)) - elif len(vset) > 1: # multiple versions available, merge - merged_text, sources = vset.merge(self._oref.index_node) #todo: For commentaries, this merges the whole chapter. It may show up as merged, even if our part is not merged. + #todo: Should this instance, and the non-merge below, be made saveable? + else: # multiple versions available, merge + if exclude_copyrighted: + vset.remove(Version.is_copyrighted) + merged_text, sources = vset.merge(self._oref.index_node, prioritized_vtitle=prioritized_vtitle) #todo: For commentaries, this merges the whole chapter. It may show up as merged, even if our part is not merged. self.text = self.trim_text(merged_text) if len(set(sources)) == 1: for v in vset: @@ -1799,13 +1831,16 @@ def _choose_version_by_lang(self) -> None: self._versions = vset.array() def __str__(self): - args = f"{self._oref}, {self.actual_lang}" + args = "{}, {}".format(self._oref, self.lang) if self.vtitle: - args += f", {self.vtitle}" + args += ", {}".format(self.vtitle) return args def __repr__(self): # Wanted to use orig_tref, but repr can not include Unicode - return f"{self.__class__.__name__}({self.__str__()})" + args = "{}, {}".format(self._oref, self.lang) + if self.vtitle: + args += ", {}".format(self.vtitle) + return "{}({})".format(self.__class__.__name__, args) def version_ids(self): if self._version_ids is None: @@ -1826,6 +1861,55 @@ def ja(self, remove_html=False): else: return JaggedTextArray(self.text) + def save(self, force_save=False): + """ + For editing in place (i.e. self.text[3] = "Some text"), it is necessary to set force_save to True. This is + because by editing in place, both the self.text and the self._original_text fields will get changed, + causing the save to abort. + :param force_save: If set to True, will force a save even if no change was detected in the text. + :return: + """ + assert self._saveable, "Tried to save a read-only text: {}".format(self._oref.normal()) + assert not self._oref.is_range(), "Only non-range references can be saved: {}".format(self._oref.normal()) + #may support simple ranges in the future. + #self._oref.is_range() and self._oref.range_index() == len(self._oref.sections) - 1 + if not force_save: + if self.text == self._original_text: + logger.warning("Aborted save of {}. No change in text.".format(self._oref.normal())) + return False + + self._validate() + self._sanitize() + self._trim_ending_whitespace() + + if not self.version(): + self.full_version = Version( + { + "chapter": self._oref.index.nodes.create_skeleton(), + "versionTitle": self.vtitle, + "versionSource": self.versionSource, + "language": self.lang, + "title": self._oref.index.title + } + ) + else: + self.full_version = Version().load({"title": self._oref.index.title, "language": self.lang, "versionTitle": self.vtitle}) + assert self.full_version, "Failed to load Version record for {}, {}".format(self._oref.normal(), self.vtitle) + if self.versionSource: + self.full_version.versionSource = self.versionSource # hack + + content = self.full_version.sub_content(self._oref.index_node.version_address()) + self._pad(content) + self.full_version.sub_content(self._oref.index_node.version_address(), [i - 1 for i in self._oref.sections], self.text) + + self._check_available_text_pre_save() + + self.full_version.save() + self._oref.recalibrate_next_prev_refs(len(self.text)) + self._update_link_language_availability() + + return self + def _pad(self, content): """ Pads the passed content to the dimension of self._oref. @@ -1851,6 +1935,61 @@ def _pad(self, content): if pos < self._ref_depth - 2 and isinstance(parent_content[val - 1], str): parent_content[val - 1] = [parent_content[val - 1]] + def _check_available_text_pre_save(self): + """ + Stores the availability of this text in before a save is made, + so that we can know if segments have been added or deleted overall. + """ + self._available_text_pre_save = {} + langs_checked = [self.lang] # swtich to ["en", "he"] when global availability checks are needed + for lang in langs_checked: + try: + self._available_text_pre_save[lang] = self._oref.text(lang=lang).text + except NoVersionFoundError: + self._available_text_pre_save[lang] = [] + + def _check_available_segments_changed_post_save(self, lang=None): + """ + Returns a list of tuples containing a Ref and a boolean availability + for each Ref that was either made available or unavailble for `lang`. + If `lang` is None, returns changed availability across all langauges. + """ + if lang: + old_refs_available = self._text_to_ref_available(self._available_text_pre_save[self.lang]) + else: + # Looking for availability of in all langauges, merge results of Hebrew and English + old_en_refs_available = self._text_to_ref_available(self._available_text_pre_save["en"]) + old_he_refs_available = self._text_to_ref_available(self._available_text_pre_save["he"]) + zipped = list(itertools.zip_longest(old_en_refs_available, old_he_refs_available)) + old_refs_available = [] + for item in zipped: + en, he = item[0], item[1] + ref = en[0] if en else he[0] + old_refs_available.append((ref, (en and en[1] or he and he[1]))) + + new_refs_available = self._text_to_ref_available(self.text) + + changed = [] + zipped = list(itertools.zip_longest(old_refs_available, new_refs_available)) + for item in zipped: + old_text, new_text = item[0], item[1] + had_previously = old_text and old_text[1] + have_now = new_text and new_text[1] + + if not had_previously and have_now: + changed.append(new_text) + elif had_previously and not have_now: + # Current save is deleting a line of text, but it could still be + # available in a different version for this language. Check again. + if lang: + text_still_available = bool(old_text[0].text(lang=lang).text) + else: + text_still_available = bool(old_text[0].text("en").text) or bool(old_text[0].text("he").text) + if not text_still_available: + changed.append([old_text[0], False]) + + return changed + def _text_to_ref_available(self, text): """Converts a JaggedArray of text to flat list of (Ref, bool) if text is availble""" flat = JaggedArray(text).flatten_to_array_with_indices() @@ -1864,6 +2003,18 @@ def _text_to_ref_available(self, text): refs_available += [[ref, available]] return refs_available + def _update_link_language_availability(self): + """ + Check if current save has changed the overall availabilty of text for refs + in this language, pass refs to update revelant links if so. + """ + changed = self._check_available_segments_changed_post_save(lang=self.lang) + + if len(changed): + from . import link + for change in changed: + link.update_link_language_availabiliy(change[0], self.lang, change[1]) + def _validate(self): """ validate that depth/breadth of the TextChunk.text matches depth/breadth of the Ref @@ -1874,8 +2025,8 @@ def _validate(self): implied_depth = ref_depth + posted_depth if implied_depth != self._oref.index_node.depth: raise InputError( - f"Text Structure Mismatch. The stored depth of {self._oref.index_node.full_title()} is {self._oref.index_node.depth}, \ - but the text posted to {self._oref.normal()} implies a depth of {implied_depth}." + "Text Structure Mismatch. The stored depth of {} is {}, but the text posted to {} implies a depth of {}." + .format(self._oref.index_node.full_title(), self._oref.index_node.depth, self._oref.normal(), implied_depth) ) #validate that length of the array matches length of the ref @@ -1884,18 +2035,20 @@ def _validate(self): span_size = self._oref.span_size() if posted_depth == 0: #possible? raise InputError( - f"Text Structure Mismatch. {self._oref.normal()} implies a length of {span_size} sections, but the text posted is a string." + "Text Structure Mismatch. {} implies a length of {} sections, but the text posted is a string." + .format(self._oref.normal(), span_size) ) elif posted_depth == 1: #possible? raise InputError( - f"Text Structure Mismatch. {self._oref.normal()} implies a length of {span_size} sections, but the text posted is a simple list." + "Text Structure Mismatch. {} implies a length of {} sections, but the text posted is a simple list." + .format(self._oref.normal(), span_size) ) else: posted_length = len(self.text) if posted_length != span_size: raise InputError( - f"Text Structure Mismatch. {self._oref.normal()} implies a length of {span_size} sections, \ - but the text posted has {posted_length} elements." + "Text Structure Mismatch. {} implies a length of {} sections, but the text posted has {} elements." + .format(self._oref.normal(), span_size, posted_length) ) #todo: validate last section size if provided @@ -1903,21 +2056,23 @@ def _validate(self): range_length = self._oref.range_size() if posted_depth == 0: raise InputError( - f"Text Structure Mismatch. {self._oref.normal()} implies a length of {range_length}, but the text posted is a string." + "Text Structure Mismatch. {} implies a length of {}, but the text posted is a string." + .format(self._oref.normal(), range_length) ) elif posted_depth == 1: posted_length = len(self.text) if posted_length != range_length: raise InputError( - f"Text Structure Mismatch. {self._oref.normal()} implies a length of {range_length}, \ - but the text posted has {posted_length} elements." + "Text Structure Mismatch. {} implies a length of {}, but the text posted has {} elements." + .format(self._oref.normal(), range_length, posted_length) ) else: # this should never happen. The depth check should catch it. raise InputError( - f"Text Structure Mismatch. {self._oref.normal()} implies an simple array of length {range_length}, \ - but the text posted has depth {posted_depth}." + "Text Structure Mismatch. {} implies an simple array of length {}, but the text posted has depth {}." + .format(self._oref.normal(), range_length, posted_depth) ) + #maybe use JaggedArray.subarray()? def trim_text(self, txt): """ Trims a text loaded from Version record with self._oref.part_projection() to the specifications of self._oref @@ -1986,13 +2141,9 @@ def has_manually_wrapped_refs(self): # merged version return False - def get_language(self): - #method for getting a different lang attribue in old TextChunck and RangeText - #can be removed after merging and all uses can be replaced by self.acutal_lang - return self.actual_lang - def nonempty_segment_refs(self): """ + :return: list of segment refs with content in this TextChunk """ r = self._oref @@ -2004,7 +2155,7 @@ def nonempty_segment_refs(self): else: input_refs = [r] for temp_ref in input_refs: - temp_tc = temp_ref.text(lang=self.get_language(), vtitle=self.vtitle) + temp_tc = temp_ref.text(lang=self.lang, vtitle=self.vtitle) ja = temp_tc.ja() jarray = ja.mask().array() @@ -2033,9 +2184,9 @@ def find_string(self, regex_str, cleaner=lambda x: x, strict=True): text_list = [x for x in self.ja().flatten_to_array() if len(x) > 0] if len(text_list) != len(ref_list): if strict: - raise ValueError(f"The number of refs doesn't match the number of starting words. len(refs)={len(ref_list)} len(inds)={len(text_list)}") + raise ValueError("The number of refs doesn't match the number of starting words. len(refs)={} len(inds)={}".format(len(ref_list),len(ind_list))) else: - print(f"Warning: The number of refs doesn't match the number of starting words. len(refs)={len(ref_list)} len(inds)={len(text_list)}") + print("Warning: The number of refs doesn't match the number of starting words. len(refs)={} len(inds)={} {}".format(len(ref_list),len(ind_list),str(self._oref))) matches = [] for r, t in zip(ref_list, text_list): @@ -2068,9 +2219,9 @@ def text_index_map(self, tokenizer=lambda x: re.split(r'\s+', x), strict=True, r if len(ind_list) != len(ref_list): if strict: - raise ValueError(f"The number of refs doesn't match the number of starting words. len(refs)={len(ref_list)} len(inds)={len(ind_list)}") + raise ValueError("The number of refs doesn't match the number of starting words. len(refs)={} len(inds)={}".format(len(ref_list),len(ind_list))) else: - print(f"Warning: The number of refs doesn't match the number of starting words. len(refs)={len(ref_list)} len(inds)={len(ind_list)}") + print("Warning: The number of refs doesn't match the number of starting words. len(refs)={} len(inds)={} {}".format(len(ref_list),len(ind_list),str(self._oref))) if len(ind_list) > len(ref_list): ind_list = ind_list[:len(ref_list)] else: @@ -2082,212 +2233,6 @@ def text_index_map(self, tokenizer=lambda x: re.split(r'\s+', x), strict=True, r return ind_list, ref_list, total_len -class TextChunk(TextRange, metaclass=TextFamilyDelegator): - """ - A chunk of text corresponding to the provided :class:`Ref`, language, and optional version name. - If it is possible to get a more complete text by merging multiple versions, a merged result will be returned. - - :param oref: :class:`Ref` - :param lang: "he" or "en". "he" means all rtl languages and "en" means all ltr languages - :param vtitle: optional. Title of the version desired. - :param actual_lang: optional. if vtitle isn't specified, prefer to find a version with ISO language `actual_lang`. As opposed to `lang` which can only be "he" or "en", `actual_lang` can be any valid 2 letter ISO language code. - """ - - text_attr = "text" - - def __init__(self, oref, lang="en", vtitle=None, exclude_copyrighted=False, actual_lang=None, fallback_on_default_version=False): - """ - :param oref: - :type oref: Ref - :param lang: "he" or "en" - :param vtitle: - :return: - """ - - self.lang = lang - super(TextChunk, self).__init__(oref=oref, vtitle=vtitle, exclude_copyrighted=exclude_copyrighted, actual_lang=actual_lang, fallback_on_default_version=fallback_on_default_version) - - def _choose_version(self, exclude_copyrighted, actual_lang, fallback_on_default_version): - if self.lang and self.vtitle and not fallback_on_default_version: - self._saveable = True - v = Version().load({"title": self._oref.index.title, "language": self.lang, "versionTitle": self.vtitle}, self._oref.part_projection()) - if exclude_copyrighted and v.is_copyrighted(): - raise InputError(f"Can not provision copyrighted text. {self._oref.normal()} ({self.vtitle}/{self.lang})") - if v: - self._versions += [v] - self.text = self._original_text = self.trim_text(v.content_node(self._oref.index_node)) - elif self.lang: - if actual_lang is not None: - self._choose_version_by_lang(self.lang, exclude_copyrighted, actual_lang, prioritized_vtitle=self.vtitle) - else: - self._choose_version_by_lang(self.lang, exclude_copyrighted, prioritized_vtitle=self.vtitle) - else: - raise Exception("TextChunk requires a language.") - - def _choose_version_by_lang(self, lang: str, exclude_copyrighted: bool, actual_lang: str = None, prioritized_vtitle: str = None) -> None: - if prioritized_vtitle: - actual_lang = None - vset = VersionSet(self._oref.condition_query(lang, actual_lang), proj=self._oref.part_projection()) - if len(vset) == 0: - if VersionSet({"title": self._oref.index.title}).count() == 0: - raise NoVersionFoundError("No text record found for '{}'".format(self._oref.index.title)) - return - if len(vset) == 1: - v = vset[0] - if exclude_copyrighted and v.is_copyrighted(): - raise InputError("Can not provision copyrighted text. {} ({}/{})".format(oref.normal(), v.versionTitle, v.language)) - self._versions += [v] - self.text = self.trim_text(v.content_node(self._oref.index_node)) - #todo: Should this instance, and the non-merge below, be made saveable? - else: # multiple versions available, merge - if exclude_copyrighted: - vset.remove(Version.is_copyrighted) - merged_text, sources = vset.merge(self._oref.index_node, prioritized_vtitle=prioritized_vtitle) #todo: For commentaries, this merges the whole chapter. It may show up as merged, even if our part is not merged. - self.text = self.trim_text(merged_text) - if len(set(sources)) == 1: - for v in vset: - if v.versionTitle == sources[0]: - self._versions += [v] - break - else: - self.sources = sources - self.is_merged = True - self._versions = vset.array() - - def __str__(self): - args = "{}, {}".format(self._oref, self.lang) - if self.vtitle: - args += ", {}".format(self.vtitle) - return args - - def __repr__(self): # Wanted to use orig_tref, but repr can not include Unicode - args = "{}, {}".format(self._oref, self.lang) - if self.vtitle: - args += ", {}".format(self.vtitle) - return "{}({})".format(self.__class__.__name__, args) - - def save(self, force_save=False): - """ - For editing in place (i.e. self.text[3] = "Some text"), it is necessary to set force_save to True. This is - because by editing in place, both the self.text and the self._original_text fields will get changed, - causing the save to abort. - :param force_save: If set to True, will force a save even if no change was detected in the text. - :return: - """ - assert self._saveable, "Tried to save a read-only text: {}".format(self._oref.normal()) - assert not self._oref.is_range(), "Only non-range references can be saved: {}".format(self._oref.normal()) - #may support simple ranges in the future. - #self._oref.is_range() and self._oref.range_index() == len(self._oref.sections) - 1 - if not force_save: - if self.text == self._original_text: - logger.warning("Aborted save of {}. No change in text.".format(self._oref.normal())) - return False - - self._validate() - self._sanitize() - self._trim_ending_whitespace() - - if not self.version(): - self.full_version = Version( - { - "chapter": self._oref.index.nodes.create_skeleton(), - "versionTitle": self.vtitle, - "versionSource": self.versionSource, - "language": self.lang, - "title": self._oref.index.title - } - ) - else: - self.full_version = Version().load({"title": self._oref.index.title, "language": self.lang, "versionTitle": self.vtitle}) - assert self.full_version, "Failed to load Version record for {}, {}".format(self._oref.normal(), self.vtitle) - if self.versionSource: - self.full_version.versionSource = self.versionSource # hack - - content = self.full_version.sub_content(self._oref.index_node.version_address()) - self._pad(content) - self.full_version.sub_content(self._oref.index_node.version_address(), [i - 1 for i in self._oref.sections], self.text) - - self._check_available_text_pre_save() - - self.full_version.save() - self._oref.recalibrate_next_prev_refs(len(self.text)) - self._update_link_language_availability() - - return self - - def _check_available_text_pre_save(self): - """ - Stores the availability of this text in before a save is made, - so that we can know if segments have been added or deleted overall. - """ - self._available_text_pre_save = {} - langs_checked = [self.lang] # swtich to ["en", "he"] when global availability checks are needed - for lang in langs_checked: - try: - self._available_text_pre_save[lang] = self._oref.text(lang=lang).text - except NoVersionFoundError: - self._available_text_pre_save[lang] = [] - - def _check_available_segments_changed_post_save(self, lang=None): - """ - Returns a list of tuples containing a Ref and a boolean availability - for each Ref that was either made available or unavailble for `lang`. - If `lang` is None, returns changed availability across all langauges. - """ - if lang: - old_refs_available = self._text_to_ref_available(self._available_text_pre_save[self.lang]) - else: - # Looking for availability of in all langauges, merge results of Hebrew and English - old_en_refs_available = self._text_to_ref_available(self._available_text_pre_save["en"]) - old_he_refs_available = self._text_to_ref_available(self._available_text_pre_save["he"]) - zipped = list(itertools.zip_longest(old_en_refs_available, old_he_refs_available)) - old_refs_available = [] - for item in zipped: - en, he = item[0], item[1] - ref = en[0] if en else he[0] - old_refs_available.append((ref, (en and en[1] or he and he[1]))) - - new_refs_available = self._text_to_ref_available(self.text) - - changed = [] - zipped = list(itertools.zip_longest(old_refs_available, new_refs_available)) - for item in zipped: - old_text, new_text = item[0], item[1] - had_previously = old_text and old_text[1] - have_now = new_text and new_text[1] - - if not had_previously and have_now: - changed.append(new_text) - elif had_previously and not have_now: - # Current save is deleting a line of text, but it could still be - # available in a different version for this language. Check again. - if lang: - text_still_available = bool(old_text[0].text(lang=lang).text) - else: - text_still_available = bool(old_text[0].text("en").text) or bool(old_text[0].text("he").text) - if not text_still_available: - changed.append([old_text[0], False]) - - return changed - - def _update_link_language_availability(self): - """ - Check if current save has changed the overall availabilty of text for refs - in this language, pass refs to update revelant links if so. - """ - changed = self._check_available_segments_changed_post_save(lang=self.lang) - - if len(changed): - from . import link - for change in changed: - link.update_link_language_availabiliy(change[0], self.lang, change[1]) - - def get_language(self): - #method for getting a different lang attribue in old TextChunck and RangeText - #can be removed after merging and all uses can be replaced by self.acutal_lang - return self.lang - - class VirtualTextChunk(AbstractTextRecord): """ Delegated from TextChunk From e90b0e7e8ef9ecef8642412a349a7a530ce24051 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 23 Aug 2023 15:17:56 +0300 Subject: [PATCH 070/260] feat(api texts): new TextRange class for getting text. --- sefaria/model/text.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 8ca31587f5..54254f0867 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1739,6 +1739,29 @@ def __call__(cls, *args, **kwargs): return super(TextFamilyDelegator, cls).__call__(*args, **kwargs) +class TextRange(AbstractTextRecord, metaclass=TextFamilyDelegator): + + def __init__(self, oref, lang, vtitle): + if isinstance(oref.index_node, JaggedArrayNode): + self.oref = oref + else: + child_ref = oref.default_child_ref() + if child_ref == oref: + raise InputError("Can not get TextChunk at this level, please provide a more precise reference") + self.oref = child_ref + self.lang = lang + self.vtitle = vtitle + self._text = None + + def set_text(self): + self._text = Version().load({'actualLanguage': self.lang, 'versionTitle': self.vtitle}, self.oref.part_projection()) + + def get_text(self): + if not self._text: + self.set_text() + return self._text + + class TextChunk(AbstractTextRecord, metaclass=TextFamilyDelegator): """ A chunk of text corresponding to the provided :class:`Ref`, language, and optional version name. From 8750229884d170d539295712dacc5f9eebc8c486 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 23 Aug 2023 17:57:20 +0300 Subject: [PATCH 071/260] fix(api texts): fix getting text by TextRange. --- sefaria/model/text.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 54254f0867..83cdb8858c 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1751,10 +1751,34 @@ def __init__(self, oref, lang, vtitle): self.oref = child_ref self.lang = lang self.vtitle = vtitle + self._version = None #todo - what we want to do if the version doesnt exist. for now the getter will cause error self._text = None + def set_version(self): + self._version = Version().load({'actualLanguage': self.lang, 'versionTitle': self.vtitle}, self.oref.part_projection()) + + def get_version(self): + if not self._version: + self.set_version() + return self._version + + def _trim_text(self, text): + """ + part_projection trims only the upper level of the jagged array. this function trims its lower levels and get rid of 1 element arrays wrappings + """ + for s, section in enumerate(self.oref.toSections[1:], 1): #start cut form end, for cutting from the start will change the indexes + subtext = reduce(lambda x, _: x[-1], range(s), text) + del subtext[section:] + for s, section in enumerate(self.oref.sections[1:], 1): + subtext = reduce(lambda x, _: x[0], range(s), text) + del subtext[:section-1] + matching_sections = itertools.takewhile(lambda pair: pair[0] == pair[1], zip(self.oref.sections, self.oref.toSections)) + redundant_depth = len(list(matching_sections)) + return reduce(lambda x, _: x[0], range(redundant_depth), text) + def set_text(self): - self._text = Version().load({'actualLanguage': self.lang, 'versionTitle': self.vtitle}, self.oref.part_projection()) + self._text = self._trim_text(self.get_version().content_node(self.oref.index_node)) + return self._text def get_text(self): if not self._text: From 82c52dca3b03260330f95549362c01fa6c7bb3e6 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 30 Aug 2023 16:16:33 +0300 Subject: [PATCH 072/260] feat(api texts): TextRangeSet and modifying TextRange for it. --- sefaria/model/text.py | 83 ++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 83cdb8858c..65a5af3278 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1739,34 +1739,48 @@ def __call__(cls, *args, **kwargs): return super(TextFamilyDelegator, cls).__call__(*args, **kwargs) -class TextRange(AbstractTextRecord, metaclass=TextFamilyDelegator): +class TextRangeBase: - def __init__(self, oref, lang, vtitle): - if isinstance(oref.index_node, JaggedArrayNode): + def __init__(self, oref): + if isinstance(oref.index_node, JaggedArrayNode): #text cannot be SchemaNode self.oref = oref + elif oref.has_default_child(): #use default child: + self.oref = oref.default_child_ref() else: - child_ref = oref.default_child_ref() - if child_ref == oref: - raise InputError("Can not get TextChunk at this level, please provide a more precise reference") - self.oref = child_ref + raise InputError("Can not get TextRange at this level, please provide a more precise reference") + + +class TextRange(TextRangeBase): + + def __init__(self, oref, lang, vtitle): + super().__init__(oref) self.lang = lang self.vtitle = vtitle self._version = None #todo - what we want to do if the version doesnt exist. for now the getter will cause error self._text = None - def set_version(self): - self._version = Version().load({'actualLanguage': self.lang, 'versionTitle': self.vtitle}, self.oref.part_projection()) - - def get_version(self): - if not self._version: - self.set_version() + @property + def version(self): + if self._version is None: # todo if there is no version it will run any time + self._version = Version().load({'title': self.oref.index.title, 'actualLanguage': self.lang, 'versionTitle': self.vtitle}, + self.oref.part_projection()) return self._version + @version.setter + def version(self, version): #version can be used if version is already in memory + self._validate_version(version) + self._version = version + + def _validate_version(self, version): + if self.lang != version.actualLanguage or self.vtitle != version.versionTitle: + raise InputError("Given version is not matching to given language and versionTitle") + def _trim_text(self, text): """ part_projection trims only the upper level of the jagged array. this function trims its lower levels and get rid of 1 element arrays wrappings """ - for s, section in enumerate(self.oref.toSections[1:], 1): #start cut form end, for cutting from the start will change the indexes + #TODO can we get the specific text directly from mongo? + for s, section in enumerate(self.oref.toSections[1:], 1): #start cut from end, for cutting from the start will change the indexes subtext = reduce(lambda x, _: x[-1], range(s), text) del subtext[section:] for s, section in enumerate(self.oref.sections[1:], 1): @@ -1776,16 +1790,43 @@ def _trim_text(self, text): redundant_depth = len(list(matching_sections)) return reduce(lambda x, _: x[0], range(redundant_depth), text) - def set_text(self): - self._text = self._trim_text(self.get_version().content_node(self.oref.index_node)) - return self._text - - def get_text(self): - if not self._text: - self.set_text() + @property + def text(self): + if self._text is None: + self._text = self._trim_text(self.version.content_node(self.oref.index_node)) #todo if there is no version it will fail return self._text +class TextRangeSet(TextRangeBase, abst.AbstractMongoSet): + """ + this class if for getting by one query a list of TextRange like other classes inheriting from AbstractMongoSet. + but TextRange is not inheriting from AbstarctMongoRecord, so it uses Version as recordClass and + override _read_records for getting TextRange. + """ + recordClass = Version #records will be converted to TextRange by overridden _read_records method + + def __init__(self, oref, version_langs_and_vtitles): + TextRangeBase.__init__(self, oref) + query = {'$or': [ + {'$and': [ + {'title': self.oref.index.title}, + {'actualLanguage': lang}, + {'versionTitle': vtitle} + ]} + for lang, vtitle in version_langs_and_vtitles]} + abst.AbstractMongoSet.__init__(self, query=query, proj=self.oref.part_projection()) + + def _read_records(self): + if self.records is None: + super()._read_records() #this makes self.records list of Version. now we'll convert them to TextRange + for i, rec in enumerate(self.records): + text_range = TextRange(self.oref, rec.actualLanguage, rec.versionTitle) + text_range.version = rec + self.records[i] = text_range + + #TODO should override some unsupported methods of AbstractMongoSet - for now: update, save, delete, contents + + class TextChunk(AbstractTextRecord, metaclass=TextFamilyDelegator): """ A chunk of text corresponding to the provided :class:`Ref`, language, and optional version name. From 4297412cccd03be41df2bd96462a41593c451349 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 3 Sep 2023 12:58:42 +0300 Subject: [PATCH 073/260] refactor(api texts): replace TextHandler (and TextRangeSet) by TextManager. --- api/views.py | 25 +++++- sefaria/model/text.py | 40 +--------- .../model/text_manager.py | 76 +++++++++---------- 3 files changed, 57 insertions(+), 84 deletions(-) rename api/texts_api.py => sefaria/model/text_manager.py (65%) diff --git a/api/views.py b/api/views.py index 9678ba210a..5a3eb6a1dd 100644 --- a/api/views.py +++ b/api/views.py @@ -1,8 +1,9 @@ from sefaria.model import * -from .texts_api import TextsForClientHandler +from sefaria.model.text_manager import TextManager from sefaria.client.util import jsonResponse from django.views import View from typing import List +from .api_warnings import * class Text(View): @@ -22,13 +23,29 @@ def split_piped_params(params_string) -> List[str]: params[1] = params[1].replace('_', ' ') return params + def _handle_warnings(self, data): + data['warnings'] = [] + for lang, vtitle in data['missings']: + if lang == 'source': + warning = APINoSourceText(self.oref) + elif vtitle and vtitle != 'all': + warning = APINoVersion(self.oref, vtitle, lang) + else: + warning = APINoLanguageVersion(self.oref, data['availabe_langs']) + representing_string = f'{lang}|{vtitle}' if vtitle else lang + data['warnings'].append({representing_string: warning.get_message()}) + data.pop('missings') + data.pop('availabe_langs') + return data + def get(self, request, *args, **kwargs): if self.oref.is_empty(): return jsonResponse({'error': f'We have no text for {self.oref}.'}, status=400) versions_params = request.GET.getlist('version', []) if not versions_params: versions_params = ['base'] - versions_params = [self.split_piped_params(param_str) + [param_str] for param_str in versions_params] - handler = TextsForClientHandler(self.oref, versions_params) - data = handler.get_versions_for_query() + versions_params = [self.split_piped_params(param_str) for param_str in versions_params] + text_manager = TextManager(self.oref, versions_params) + data = text_manager.get_versions_for_query() + data = self._handle_warnings(data) return jsonResponse(data) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 65a5af3278..1897d4c6d0 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1739,21 +1739,15 @@ def __call__(cls, *args, **kwargs): return super(TextFamilyDelegator, cls).__call__(*args, **kwargs) -class TextRangeBase: +class TextRange: - def __init__(self, oref): + def __init__(self, oref, lang, vtitle): if isinstance(oref.index_node, JaggedArrayNode): #text cannot be SchemaNode self.oref = oref elif oref.has_default_child(): #use default child: self.oref = oref.default_child_ref() else: raise InputError("Can not get TextRange at this level, please provide a more precise reference") - - -class TextRange(TextRangeBase): - - def __init__(self, oref, lang, vtitle): - super().__init__(oref) self.lang = lang self.vtitle = vtitle self._version = None #todo - what we want to do if the version doesnt exist. for now the getter will cause error @@ -1797,36 +1791,6 @@ def text(self): return self._text -class TextRangeSet(TextRangeBase, abst.AbstractMongoSet): - """ - this class if for getting by one query a list of TextRange like other classes inheriting from AbstractMongoSet. - but TextRange is not inheriting from AbstarctMongoRecord, so it uses Version as recordClass and - override _read_records for getting TextRange. - """ - recordClass = Version #records will be converted to TextRange by overridden _read_records method - - def __init__(self, oref, version_langs_and_vtitles): - TextRangeBase.__init__(self, oref) - query = {'$or': [ - {'$and': [ - {'title': self.oref.index.title}, - {'actualLanguage': lang}, - {'versionTitle': vtitle} - ]} - for lang, vtitle in version_langs_and_vtitles]} - abst.AbstractMongoSet.__init__(self, query=query, proj=self.oref.part_projection()) - - def _read_records(self): - if self.records is None: - super()._read_records() #this makes self.records list of Version. now we'll convert them to TextRange - for i, rec in enumerate(self.records): - text_range = TextRange(self.oref, rec.actualLanguage, rec.versionTitle) - text_range.version = rec - self.records[i] = text_range - - #TODO should override some unsupported methods of AbstractMongoSet - for now: update, save, delete, contents - - class TextChunk(AbstractTextRecord, metaclass=TextFamilyDelegator): """ A chunk of text corresponding to the provided :class:`Ref`, language, and optional version name. diff --git a/api/texts_api.py b/sefaria/model/text_manager.py similarity index 65% rename from api/texts_api.py rename to sefaria/model/text_manager.py index 3f15d2f02f..c8508c3f5a 100644 --- a/api/texts_api.py +++ b/sefaria/model/text_manager.py @@ -1,69 +1,62 @@ import django django.setup() +from sefaria.model import * from sefaria.utils.hebrew import hebrew_term -from .api_warnings import * from typing import List - -class TextsForClientHandler(): - """ - process api calls for text - return_obj is dict that includes in its root: - ref and index data - 'versions' - list of versions details and text - 'warning' - for any version_params that has an warning - """ - +class TextManager: ALL = 'all' BASE = 'base' SOURCE = 'source' + TRANSLATION = 'translation' def __init__(self, oref: Ref, versions_params: List[List[str]]): self.versions_params = versions_params self.oref = oref self.handled_version_params = [] - self.all_versions = self.oref.version_list() - self.return_obj = {'versions': [], 'warnings': []} + self.all_versions = self.oref.versionset() + self.return_obj = { + 'versions': [], + 'missings': [], + 'availabe_langs': sorted({v.actualLanguage for v in self.all_versions}) + } - def _handle_warnings(self, lang: str, vtitle: str, params_str: str) -> None: - if lang == self.SOURCE: - warning = APINoSourceText(self.oref) - elif vtitle and vtitle != self.ALL: - warning = APINoVersion(self.oref, vtitle, lang) - else: - availabe_langs = {v['actualLanguage'] for v in self.all_versions} - warning = APINoLanguageVersion(self.oref, sorted(availabe_langs)) - representing_string = params_str or f'{lang}|{vtitle}' - self.return_obj['warnings'].append({ - representing_string: warning.get_message() - }) + def _append_version(self, version): + #TODO part of this function duplicate the functionality of Ref.versionlist(). maybe we should mvoe it to Version + fields = Version.optional_attrs + Version.required_attrs + for attr in ['chapter', 'title', 'language']: + fields.remove(attr) + version_details = {f: getattr(version, f, "") for f in fields} + text_range = TextRange(self.oref, version.actualLanguage, version.versionTitle) + text_range.version = version + version_details['text'] = text_range.text + if self.oref.is_book_level(): + first_section_ref = version.first_section_ref() or version.get_index().nodes.first_leaf().first_section_ref() + version_details['firstSectionRef'] = first_section_ref.normal() + self.return_obj['versions'].append(version_details) - def _append_required_versions(self, lang: str, vtitle: str, params_str: str) -> None: + def _append_required_versions(self, lang: str, vtitle: str) -> None: if lang == self.BASE: - lang_condition = lambda v: v['isBaseText2'] #temporal name + lang_condition = lambda v: getattr(v, 'isBaseText2', False) # temporal name elif lang == self.SOURCE: - lang_condition = lambda v: v['isSource'] + lang_condition = lambda v: getattr(v, 'isSource', False) + elif lang == self.TRANSLATION: + lang_condition = lambda v: not getattr(v, 'isSource', False) elif lang: - lang_condition = lambda v: v['actualLanguage'] == lang + lang_condition = lambda v: v.actualLanguage == lang else: lang_condition = lambda v: True if vtitle and vtitle != self.ALL: - versions = [v for v in self.all_versions if lang_condition(v) and v['versionTitle'] == vtitle] + versions = [v for v in self.all_versions if lang_condition(v) and v.versionTitle == vtitle] else: versions = [v for v in self.all_versions if lang_condition(v)] if vtitle != self.ALL and versions: - versions = [max(versions, key=lambda v: v['priority'] or 0)] + versions = [max(versions, key=lambda v: getattr(v, 'priority', 0))] for version in versions: if version not in self.return_obj['versions']: #do not return the same version even if included in two different version params - self.return_obj['versions'].append(version) + self._append_version(version) if not versions: - self._handle_warnings(lang, vtitle, params_str) - - def _add_text_to_versions(self) -> None: - for version in self.return_obj['versions']: - version.pop('title') - version.pop('language') #should be removed after language is removed from attrs - version['text'] = TextRange(self.oref, version['actualLanguage'], version['versionTitle']).text + self.return_obj['missings'].append((lang, vtitle)) def _add_ref_data_to_return_obj(self) -> None: oref = self.oref @@ -119,9 +112,8 @@ def _add_node_data_to_return_obj(self) -> None: }) def get_versions_for_query(self) -> dict: - for lang, vtitle, params_str in self.versions_params: - self._append_required_versions(lang, vtitle, params_str) - self._add_text_to_versions() + for lang, vtitle in self.versions_params: + self._append_required_versions(lang, vtitle) self._add_ref_data_to_return_obj() self._add_index_data_to_return_obj() self._add_node_data_to_return_obj() From b9fce2822a666e69dd99c24dac70c26e08010a15 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 3 Sep 2023 13:13:36 +0300 Subject: [PATCH 074/260] feat(api texts): warning for no translation. --- api/api_warnings.py | 7 +++++++ api/views.py | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/api/api_warnings.py b/api/api_warnings.py index 40cb37b1cc..586802a664 100644 --- a/api/api_warnings.py +++ b/api/api_warnings.py @@ -52,3 +52,10 @@ class APINoSourceText(TextsAPIResponseMessage): def __init__(self, oref: Ref): self.warning_code = APIWarningCode.APINoSourceText.value self.message = f'We do not have the source text for {oref}' + + +class APINoTranslationText(TextsAPIResponseMessage): + + def __init__(self, oref: Ref): + self.warning_code = APIWarningCode.APINoSourceText.value + self.message = f'We do not have a translation for {oref}' diff --git a/api/views.py b/api/views.py index 5a3eb6a1dd..9577f5967d 100644 --- a/api/views.py +++ b/api/views.py @@ -2,7 +2,6 @@ from sefaria.model.text_manager import TextManager from sefaria.client.util import jsonResponse from django.views import View -from typing import List from .api_warnings import * @@ -28,6 +27,8 @@ def _handle_warnings(self, data): for lang, vtitle in data['missings']: if lang == 'source': warning = APINoSourceText(self.oref) + elif lang == 'translation': + warning = APINoTranslationText(self.oref) elif vtitle and vtitle != 'all': warning = APINoVersion(self.oref, vtitle, lang) else: From fbd334a0b26bc1a922b22ccfa882bb05f314fadf Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 3 Sep 2023 14:23:50 +0300 Subject: [PATCH 075/260] fix(api texts): error code for no translation. --- api/api_warnings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/api_warnings.py b/api/api_warnings.py index 586802a664..7c8b5ccb44 100644 --- a/api/api_warnings.py +++ b/api/api_warnings.py @@ -8,6 +8,7 @@ class APIWarningCode(Enum): APINoVersion = 101 APINoLanguageVersion = 102 APINoSourceText = 103 + APINoTranslationText = 104 """ classes for data warnings in API calls. @@ -57,5 +58,5 @@ def __init__(self, oref: Ref): class APINoTranslationText(TextsAPIResponseMessage): def __init__(self, oref: Ref): - self.warning_code = APIWarningCode.APINoSourceText.value + self.warning_code = APIWarningCode.APINoTranslationText.value self.message = f'We do not have a translation for {oref}' From d2c76920c415956077643cd2ce36f363d4194a0c Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Sun, 3 Sep 2023 14:23:58 +0300 Subject: [PATCH 076/260] test(api texts): tests for translation. --- api/tests.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/api/tests.py b/api/tests.py index 05398172a0..0bf2167dd9 100644 --- a/api/tests.py +++ b/api/tests.py @@ -33,6 +33,17 @@ def test_api_get_text_source_all(self): self.assertEqual(data["sections"], ["22a"]) self.assertEqual(data["toSections"], ["22a"]) + def test_api_get_text_translation_all(self): + response = c.get('/api/v3/texts/Shabbat.22a?version=translation|all') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertTrue(len(data["versions"]) > 1) + self.assertTrue(any(v['actualLanguage'] == 'en' for v in data["versions"])) + self.assertEqual(data["book"], "Shabbat") + self.assertEqual(data["categories"], ["Talmud", "Bavli", "Seder Moed"]) + self.assertEqual(data["sections"], ["22a"]) + self.assertEqual(data["toSections"], ["22a"]) + def test_api_get_text_lang_all(self): response = c.get('/api/v3/texts/Rashi_on_Genesis.2.3?version=en|all') self.assertEqual(200, response.status_code) @@ -114,6 +125,14 @@ def test_api_get_text_no_source(self): self.assertEqual(data['warnings'][0]['source']['warning_code'], APIWarningCode.APINoSourceText.value) self.assertEqual(data['warnings'][0]['source']['message'], 'We do not have the source text for The Book of Maccabees I 1') + def test_api_get_text_no_translation(self): + response = c.get("/api/v3/texts/Shuvi_Shuvi_HaShulamit?version=translation") + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data["versions"]), 0) + self.assertEqual(data['warnings'][0]['translation']['warning_code'], APIWarningCode.APINoTranslationText.value) + self.assertEqual(data['warnings'][0]['translation']['message'], 'We do not have a translation for Shuvi Shuvi HaShulamit') + def test_api_get_text_no_language(self): response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=en|Brenton's_Septuagint&version=sgrg|all") self.assertEqual(200, response.status_code) From 31148341a93d8edf66795090f1c64dff3b3188dc Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 6 Sep 2023 16:19:14 +0300 Subject: [PATCH 077/260] feat(api texts): make api work with lexicons. --- api/views.py | 2 +- sefaria/model/text.py | 10 ++++++++-- sefaria/model/text_manager.py | 5 +++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/api/views.py b/api/views.py index 9577f5967d..a81189f564 100644 --- a/api/views.py +++ b/api/views.py @@ -40,7 +40,7 @@ def _handle_warnings(self, data): return data def get(self, request, *args, **kwargs): - if self.oref.is_empty(): + if self.oref.is_empty() and not self.oref.index_node.is_virtual: return jsonResponse({'error': f'We have no text for {self.oref}.'}, status=400) versions_params = request.GET.getlist('version', []) if not versions_params: diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 1897d4c6d0..dac6b9a5df 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1742,7 +1742,7 @@ def __call__(cls, *args, **kwargs): class TextRange: def __init__(self, oref, lang, vtitle): - if isinstance(oref.index_node, JaggedArrayNode): #text cannot be SchemaNode + if isinstance(oref.index_node, JaggedArrayNode) or isinstance(oref.index_node, DictionaryEntryNode): #text cannot be SchemaNode self.oref = oref elif oref.has_default_child(): #use default child: self.oref = oref.default_child_ref() @@ -1787,7 +1787,10 @@ def _trim_text(self, text): @property def text(self): if self._text is None: - self._text = self._trim_text(self.version.content_node(self.oref.index_node)) #todo if there is no version it will fail + if self.oref.index_node.is_virtual: + self._text = self.oref.index_node.get_text() + else: + self._text = self._trim_text(self.version.content_node(self.oref.index_node)) #todo if there is no version it will fail return self._text @@ -4497,6 +4500,9 @@ def part_projection(self): # todo: reimplement w/ aggregation pipeline (see above) # todo: special case string 0? + if self.index_node.is_virtual: + return + projection = {k: 1 for k in Version.required_attrs + Version.optional_attrs} del projection[Version.content_attr] # Version.content_attr == "chapter" projection["_id"] = 0 diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index c8508c3f5a..6ee0219b24 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -37,7 +37,7 @@ def _append_version(self, version): def _append_required_versions(self, lang: str, vtitle: str) -> None: if lang == self.BASE: - lang_condition = lambda v: getattr(v, 'isBaseText2', False) # temporal name + lang_condition = lambda v: getattr(v, 'isBaseText', False) elif lang == self.SOURCE: lang_condition = lambda v: getattr(v, 'isSource', False) elif lang == self.TRANSLATION: @@ -108,8 +108,9 @@ def _add_node_data_to_return_obj(self) -> None: 'heTitle': inode.full_title("he"), 'titleVariants': inode.all_tree_titles("en"), 'heTitleVariants': inode.all_tree_titles("he"), - 'index_offsets_by_depth': inode.trim_index_offsets_by_sections(self.oref.sections, self.oref.toSections), }) + if not inode.is_virtual: + self.return_obj['index_offsets_by_depth'] = inode.trim_index_offsets_by_sections(self.oref.sections, self.oref.toSections) def get_versions_for_query(self) -> dict: for lang, vtitle in self.versions_params: From c69e1a9733396d1dcf6c7d71807ac8d42bff4e2f Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Wed, 13 Sep 2023 12:50:18 +0300 Subject: [PATCH 078/260] feat(text api): add available_versions to the returned object. --- api/views.py | 4 ++-- sefaria/model/text_manager.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/api/views.py b/api/views.py index a81189f564..a4cfe8d294 100644 --- a/api/views.py +++ b/api/views.py @@ -32,11 +32,11 @@ def _handle_warnings(self, data): elif vtitle and vtitle != 'all': warning = APINoVersion(self.oref, vtitle, lang) else: - warning = APINoLanguageVersion(self.oref, data['availabe_langs']) + warning = APINoLanguageVersion(self.oref, data['available_langs']) representing_string = f'{lang}|{vtitle}' if vtitle else lang data['warnings'].append({representing_string: warning.get_message()}) data.pop('missings') - data.pop('availabe_langs') + data.pop('available_langs') return data def get(self, request, *args, **kwargs): diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 6ee0219b24..6beb63c8af 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -15,10 +15,14 @@ def __init__(self, oref: Ref, versions_params: List[List[str]]): self.oref = oref self.handled_version_params = [] self.all_versions = self.oref.versionset() + + fields = Version.optional_attrs + Version.required_attrs + fields.remove('chapter') # not metadata self.return_obj = { 'versions': [], 'missings': [], - 'availabe_langs': sorted({v.actualLanguage for v in self.all_versions}) + 'available_langs': sorted({v.actualLanguage for v in self.all_versions}), + 'available_versions': [{f: getattr(v, f, "") for f in fields} for v in self.all_versions] } def _append_version(self, version): From 3830f834a9eca5fc04b79c50bfaf5b57b21b4104 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Thu, 14 Sep 2023 12:32:02 +0300 Subject: [PATCH 079/260] fix(TextRange): add underscore. --- sefaria/model/text.py | 50 +++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index dac6b9a5df..a3f30edf07 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1741,7 +1741,7 @@ def __call__(cls, *args, **kwargs): class TextRange: - def __init__(self, oref, lang, vtitle): + def __init__(self, oref, lang, vtitle, merge_versions=False): if isinstance(oref.index_node, JaggedArrayNode) or isinstance(oref.index_node, DictionaryEntryNode): #text cannot be SchemaNode self.oref = oref elif oref.has_default_child(): #use default child: @@ -1750,24 +1750,33 @@ def __init__(self, oref, lang, vtitle): raise InputError("Can not get TextRange at this level, please provide a more precise reference") self.lang = lang self.vtitle = vtitle - self._version = None #todo - what we want to do if the version doesnt exist. for now the getter will cause error + self.merge_versions = merge_versions + self._versions = [] self._text = None + self.sources = None @property - def version(self): - if self._version is None: # todo if there is no version it will run any time - self._version = Version().load({'title': self.oref.index.title, 'actualLanguage': self.lang, 'versionTitle': self.vtitle}, - self.oref.part_projection()) - return self._version - - @version.setter - def version(self, version): #version can be used if version is already in memory - self._validate_version(version) - self._version = version - - def _validate_version(self, version): - if self.lang != version.actualLanguage or self.vtitle != version.versionTitle: - raise InputError("Given version is not matching to given language and versionTitle") + def versions(self): + if self._versions == []: + condition_query = self.oref.condition_query(self.lang) if self.merge_versions else \ + {'title': self.oref.index.title, 'actualLanguage': self.lang, 'versionTitle': self.vtitle} + self._versions = VersionSet(condition_query, proj=self.oref.part_projection()) + return self._versions + + @versions.setter + def versions(self, versions): + self._validate_versions(versions) + self._versions = versions + + def _validate_versions(self, versions): + if not self.merge_versions and len(versions) > 1: + raise InputError("Got many versions instead of one") + for version in versions: + condition = version.title == self.oref.index.title and version.actualLanguage == self.lang + if not self.merge_versions: + condition = condition and version.versionTitle == self.vtitle + if not condition: + raise InputError(f"Given version, {version}, is not matching to title, language or versionTitle") def _trim_text(self, text): """ @@ -1787,10 +1796,15 @@ def _trim_text(self, text): @property def text(self): if self._text is None: - if self.oref.index_node.is_virtual: + if self.merge_versions and len(self.versions) > 1: + merged_text, sources = self.versions.merge(self.oref.index_node, prioritized_vtitle=self.vtitle) + self._text = self._trim_text(merged_text) + if len(sources) > 1: + self.sources = sources + elif self.oref.index_node.is_virtual: self._text = self.oref.index_node.get_text() else: - self._text = self._trim_text(self.version.content_node(self.oref.index_node)) #todo if there is no version it will fail + self._text = self._trim_text(self.versions[0].content_node(self.oref.index_node)) #todo if there is no version it will fail return self._text From 825081625c04816cae3773c16b77de321f922767 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Thu, 14 Sep 2023 14:25:47 +0300 Subject: [PATCH 080/260] feat(text api): add merge option to API. --- api/views.py | 3 ++- sefaria/model/text_manager.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/api/views.py b/api/views.py index a4cfe8d294..241c95e290 100644 --- a/api/views.py +++ b/api/views.py @@ -46,7 +46,8 @@ def get(self, request, *args, **kwargs): if not versions_params: versions_params = ['base'] versions_params = [self.split_piped_params(param_str) for param_str in versions_params] - text_manager = TextManager(self.oref, versions_params) + fill_in_missing_segments = request.GET.get('fill_in_missing_segments', False) + text_manager = TextManager(self.oref, versions_params, fill_in_missing_segments) data = text_manager.get_versions_for_query() data = self._handle_warnings(data) return jsonResponse(data) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 6beb63c8af..c48c245c19 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -1,3 +1,5 @@ +import copy + import django django.setup() from sefaria.model import * @@ -10,9 +12,10 @@ class TextManager: SOURCE = 'source' TRANSLATION = 'translation' - def __init__(self, oref: Ref, versions_params: List[List[str]]): + def __init__(self, oref: Ref, versions_params: List[List[str]], fill_in_missing_segments=True): self.versions_params = versions_params self.oref = oref + self.fill_in_missing_segments = fill_in_missing_segments self.handled_version_params = [] self.all_versions = self.oref.versionset() @@ -31,8 +34,15 @@ def _append_version(self, version): for attr in ['chapter', 'title', 'language']: fields.remove(attr) version_details = {f: getattr(version, f, "") for f in fields} - text_range = TextRange(self.oref, version.actualLanguage, version.versionTitle) - text_range.version = version + text_range = TextRange(self.oref, version.actualLanguage, version.versionTitle, True) + + if self.fill_in_missing_segments: + # we need a new VersionSet of only the relevant versions for merging. copy should be better than calling for mongo + relevant_versions = copy.copy(self.all_versions) + relevant_versions.remove(lambda v: v.actualLanguage != version.actualLanguage) + else: + relevant_versions = [version] + text_range.versions = relevant_versions version_details['text'] = text_range.text if self.oref.is_book_level(): first_section_ref = version.first_section_ref() or version.get_index().nodes.first_leaf().first_section_ref() From 8468bc6eabffbc90cb6fc747ee152817e597ffe7 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Thu, 14 Sep 2023 14:45:10 +0300 Subject: [PATCH 081/260] test(text api): test the fill_in_missing_segments parameter. --- api/tests.py | 18 ++++++++++++++++++ sefaria/model/text_manager.py | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/api/tests.py b/api/tests.py index 0bf2167dd9..84734d85d6 100644 --- a/api/tests.py +++ b/api/tests.py @@ -150,3 +150,21 @@ def test_api_get_text_no_version(self): self.assertEqual(data['warnings'][0]['he|Kishkoosh']['warning_code'], APIWarningCode.APINoVersion.value) self.assertEqual(data['warnings'][0]['he|Kishkoosh']['message'], 'We do not have version named Kishkoosh with language code he for The Book of Maccabees I 1') + + def test_fill_in_missing_segments(self): + vtitle = "Maimonides' Mishneh Torah, edited by Philip Birnbaum, New York, 1967" + response = c.get(f"/api/v3/texts/Mishneh_Torah,_Sabbath_1?version=en|{vtitle}&fill_in_missing_segments=true") + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertTrue(len(data['versions'][0]['text']) > 2) + self.assertTrue(data['versions'][0].get('sources')) + self.assertEqual(data['versions'][0]['sources'][0], vtitle) + self.assertNotEqual(data['versions'][0]['sources'][2], vtitle) + + def test_without_fill_in_missing_segments(self): + vtitle = "Maimonides' Mishneh Torah, edited by Philip Birnbaum, New York, 1967" + response = c.get(f"/api/v3/texts/Mishneh_Torah,_Sabbath_1?version=en|{vtitle}") + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data['versions'][0]['text']), 2) + self.assertFalse(data['versions'][0].get('sources')) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index c48c245c19..37641d0229 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -44,6 +44,11 @@ def _append_version(self, version): relevant_versions = [version] text_range.versions = relevant_versions version_details['text'] = text_range.text + + sources = getattr(text_range, 'sources', None) + if sources is not None: + version_details['sources'] = sources + if self.oref.is_book_level(): first_section_ref = version.first_section_ref() or version.get_index().nodes.first_leaf().first_section_ref() version_details['firstSectionRef'] = first_section_ref.normal() From 5f85f3184a8dd5578a009197abfe86b953f789d5 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 19 Sep 2023 12:14:49 +0300 Subject: [PATCH 082/260] fix(TextRange): trim text in copy rather than on the version itself. --- sefaria/model/text.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index a3f30edf07..85221fd61a 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1783,6 +1783,7 @@ def _trim_text(self, text): part_projection trims only the upper level of the jagged array. this function trims its lower levels and get rid of 1 element arrays wrappings """ #TODO can we get the specific text directly from mongo? + text = copy.deepcopy(text) for s, section in enumerate(self.oref.toSections[1:], 1): #start cut from end, for cutting from the start will change the indexes subtext = reduce(lambda x, _: x[-1], range(s), text) del subtext[section:] From ba03a0239c0e388e1f6a9ffc349c69a048296add Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 19 Sep 2023 12:19:45 +0300 Subject: [PATCH 083/260] fix(text manager): filling with other versions just when asked. --- sefaria/model/text_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 37641d0229..3395c1ac5b 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -34,7 +34,7 @@ def _append_version(self, version): for attr in ['chapter', 'title', 'language']: fields.remove(attr) version_details = {f: getattr(version, f, "") for f in fields} - text_range = TextRange(self.oref, version.actualLanguage, version.versionTitle, True) + text_range = TextRange(self.oref, version.actualLanguage, version.versionTitle, self.fill_in_missing_segments) if self.fill_in_missing_segments: # we need a new VersionSet of only the relevant versions for merging. copy should be better than calling for mongo @@ -42,6 +42,7 @@ def _append_version(self, version): relevant_versions.remove(lambda v: v.actualLanguage != version.actualLanguage) else: relevant_versions = [version] + print(self.oref, version.actualLanguage, version.versionTitle, self.fill_in_missing_segments, relevant_versions) text_range.versions = relevant_versions version_details['text'] = text_range.text From 9f51b4ebdbda912085f97804dc2aedc93b646278 Mon Sep 17 00:00:00 2001 From: yishai <yishai@sefaria.org> Date: Tue, 19 Sep 2023 12:28:37 +0300 Subject: [PATCH 084/260] fix(text manager): do not return the same version even if included in two different version params. --- sefaria/model/text_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 3395c1ac5b..6b782dc660 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -73,7 +73,8 @@ def _append_required_versions(self, lang: str, vtitle: str) -> None: if vtitle != self.ALL and versions: versions = [max(versions, key=lambda v: getattr(v, 'priority', 0))] for version in versions: - if version not in self.return_obj['versions']: #do not return the same version even if included in two different version params + if all(version.actualLanguage != v['actualLanguage'] and version.versionTitle != v['versionTitle'] for v in self.return_obj['versions']): + #do not return the same version even if included in two different version params self._append_version(version) if not versions: self.return_obj['missings'].append((lang, vtitle)) From 7682f5e7542957a3a350ae01b476fd971b3ec0dc Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Wed, 27 Sep 2023 15:19:26 +0200 Subject: [PATCH 085/260] fix(database): Check for db name existence in the db server before anything --- reader/views.py | 10 +++++++++- sefaria/system/database.py | 21 ++++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/reader/views.py b/reader/views.py index 86f6843254..c862905548 100644 --- a/reader/views.py +++ b/reader/views.py @@ -4543,11 +4543,19 @@ def isNodeJsReachable(): except Exception as e: logger.warn(f"Failed node healthcheck. Error: {e}") return False + + def is_database_reachable(): + try: + from sefaria.system.database import db + return True + except SystemError as ivne: + return False - allReady = isRedisReachable() and isMultiserverReachable() and isNodeJsReachable() + allReady = isRedisReachable() and isMultiserverReachable() and isNodeJsReachable() and is_database_reachable() resp = { 'allReady': allReady, + 'dbConnected': f'Database Connection: {is_database_reachable()}', 'multiserverReady': isMultiserverReachable(), 'redisReady': isRedisReachable(), 'nodejsReady': isNodeJsReachable(), diff --git a/sefaria/system/database.py b/sefaria/system/database.py index 45879f0823..ff0f07b66e 100644 --- a/sefaria/system/database.py +++ b/sefaria/system/database.py @@ -9,6 +9,20 @@ from sefaria.settings import * +def check_db_exists(db_name): + dbnames = client.list_database_names() + return db_name in dbnames + + +def connect_to_db(db_name): + if not check_db_exists(db_name): + raise SystemError(f'Database {db_name} does not exist!') + return client[db_name] + +def get_test_db(): + return client[TEST_DB] + + if hasattr(sys, '_doc_build'): db = "" else: @@ -37,13 +51,10 @@ # Now set the db variable to point to the Sefaria database in the server if not hasattr(sys, '_called_from_test'): - db = client[SEFARIA_DB] + db = connect_to_db(SEFARIA_DB) else: - db = client[TEST_DB] - + db = connect_to_db(TEST_DB) -def get_test_db(): - return client[TEST_DB] def drop_test(): From 2decebdaf94bac486a906a9dc96fc37497c7564e Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 29 Oct 2023 12:16:01 +0200 Subject: [PATCH 086/260] refactor(text api): move 'value' from any warning class to the get_message function. --- api/api_warnings.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/api_warnings.py b/api/api_warnings.py index 7c8b5ccb44..aecf32b601 100644 --- a/api/api_warnings.py +++ b/api/api_warnings.py @@ -30,33 +30,33 @@ class for returning a message and an warning code """ def get_message(self) -> dict: - return {'warning_code': self.warning_code, + return {'warning_code': self.warning_code.value, 'message': self.message} class APINoVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, vtitle: str, lang: str): - self.warning_code = APIWarningCode.APINoVersion.value + self.warning_code = APIWarningCode.APINoVersion self.message = f'We do not have version named {vtitle} with language code {lang} for {oref}' class APINoLanguageVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, langs: List[str]): - self.warning_code = APIWarningCode.APINoLanguageVersion.value + self.warning_code = APIWarningCode.APINoLanguageVersion self.message = f'We do not have the code language you asked for {oref}. Available codes are {langs}' class APINoSourceText(TextsAPIResponseMessage): def __init__(self, oref: Ref): - self.warning_code = APIWarningCode.APINoSourceText.value + self.warning_code = APIWarningCode.APINoSourceText self.message = f'We do not have the source text for {oref}' class APINoTranslationText(TextsAPIResponseMessage): def __init__(self, oref: Ref): - self.warning_code = APIWarningCode.APINoTranslationText.value + self.warning_code = APIWarningCode.APINoTranslationText self.message = f'We do not have a translation for {oref}' From 84ee222ddc4fec35c058c7696df3eb6f5f0920d7 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 29 Oct 2023 12:16:23 +0200 Subject: [PATCH 087/260] chore(text api): remove print. --- sefaria/model/text_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 6b782dc660..66412d597c 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -42,7 +42,6 @@ def _append_version(self, version): relevant_versions.remove(lambda v: v.actualLanguage != version.actualLanguage) else: relevant_versions = [version] - print(self.oref, version.actualLanguage, version.versionTitle, self.fill_in_missing_segments, relevant_versions) text_range.versions = relevant_versions version_details['text'] = text_range.text From bb3e3b2bb99199eeccfc902d2449e970bcc1ee11 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 29 Oct 2023 14:41:26 +0200 Subject: [PATCH 088/260] test(text api): test api text for virtual node. --- api/tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/tests.py b/api/tests.py index 84734d85d6..df63d460ad 100644 --- a/api/tests.py +++ b/api/tests.py @@ -89,6 +89,13 @@ def test_api_get_text_range(self): self.assertEqual(data["sections"], ['5', '2']) self.assertEqual(data["toSections"], ['5', '4']) + def text_api_virtual_node(self): + response = c.get('/api/v3/texts/BDB, א') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data['versions']), 1) + self.assertEqual(data['versions'][0]['text'], ['<big><span dir="rtl">א</span></big> <em>Āleph</em>, first letter; in post Biblical Hebrew = numeral 1 (and so in margin of printed MT); א̈= 1000; no evidence of this usage in OT times.']) + def test_api_get_text_bad_text(self): response = c.get('/api/v3/texts/Life_of_Pi.13.13') self.assertEqual(400, response.status_code) From 1653261bca31142ee6829e66772c536eef09533a Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Sun, 29 Oct 2023 17:22:58 -0400 Subject: [PATCH 089/260] feat(strapi-cms): Teams page now uses Strapi for its data --- sites/sefaria/urls.py | 2 +- static/js/strapi_helpers/team_page.js | 202 ++++++++++++++++++++++++++ templates/static/team.html | 49 +++++++ 3 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 static/js/strapi_helpers/team_page.js create mode 100644 templates/static/team.html diff --git a/sites/sefaria/urls.py b/sites/sefaria/urls.py index 5cee2ad59c..45c2ed2a86 100644 --- a/sites/sefaria/urls.py +++ b/sites/sefaria/urls.py @@ -47,12 +47,12 @@ "word-by-word", "cloudflare_site_is_down_en", "cloudflare_site_is_down_he", + "team", ] static_pages_by_lang = [ "about", "ways-to-give", - "team" ] diff --git a/static/js/strapi_helpers/team_page.js b/static/js/strapi_helpers/team_page.js new file mode 100644 index 0000000000..6df692bfae --- /dev/null +++ b/static/js/strapi_helpers/team_page.js @@ -0,0 +1,202 @@ +const { el, mount, setChildren } = redom; + +const byLastName = (a, b) => { + const c = a.teamMemberDetails.teamName.en; + const d = b.teamMemberDetails.teamName.en; + const lastNameA = c.split(" ").pop(); + const lastNameB = d.split(" ").pop(); + return lastNameA.localeCompare(lastNameB); +}; + +const partition = (arr, prop) => + arr.reduce( + (accumulator, currentValue) => { + accumulator[prop(currentValue) ? 0 : 1].push(currentValue); + return accumulator; + }, + [[], []] + ); + +function LocalizedText(text) { + return [el("span.int-en", text.en), el("span.int-he", text.he)]; +} + +function TeamTitle(teamTitle) { + return el(".teamTitle", LocalizedText(teamTitle)); +} + +function TeamName(teamName) { + return el(".teamName", LocalizedText(teamName)); +} + +function TeamMemberDetails(teamMemberDetails) { + return el(".teamMemberDetails", [ + TeamName(teamMemberDetails.teamName), + TeamTitle(teamMemberDetails.teamTitle), + ]); +} + +function TeamMemberImage(teamMember) { + return el( + ".teamMemberImage", + el("img", { + src: teamMember.teamMemberImage, + alt: "Headshot of " + teamMember.teamMemberDetails.teamName.en, + }) + ); +} + +function TeamMember(teamMember) { + return el(".teamMember", [ + TeamMemberImage(teamMember), + TeamMemberDetails(teamMember.teamMemberDetails), + ]); +} + +function TeamMembers(teamMembers) { + return teamMembers.map((teamMember) => TeamMember(teamMember)); +} + +function Placeholders(teamMembersCount, cls) { + // Determine the number of empty spots to have as placeholders in the last row + placeholdersCount = 3 - (teamMembersCount - 3 * ~~(teamMembersCount / 3)); + return Array.from({ length: placeholdersCount }, () => + el("." + cls + ".placeholder") + ); +} + +function BoardMember(boardMember) { + return el( + ".teamBoardMember", + TeamMemberDetails(boardMember.teamMemberDetails) + ); +} + +function BoardMembers(boardMembers) { + // Separate out the chairman and co-founders for the correct ordering + let chairmanBoardMember; + let chairmanIndex = boardMembers.findIndex( + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === "chairman" + ); + if (chairmanIndex !== -1) { + chairmanBoardMember = boardMembers.splice(chairmanIndex, 1); + } + const [cofounderBoardMembers, regularBoardMembers] = partition( + boardMembers, + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === "co-founder" + ); + // Produce the nodes with the right order for the board members + // Chairman, Co-founders, rest of the board + return [ + ...(chairmanBoardMember ?? []), + ...(cofounderBoardMembers ?? []), + ...regularBoardMembers.sort(byLastName), + ].map((boardMember) => BoardMember(boardMember)); +} + +if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { + async function fetchTeamMembersJSON() { + const query = ` + query { + teamMembers(pagination: { limit: -1 }) { + data { + id + attributes { + teamName + teamTitle + isTeamBoardMember + teamMemberImage { + data { + attributes { + url + } + } + } + localizations { + data { + attributes { + locale + teamName + teamTitle + } + } + } + } + } + } + } + `; + try { + const response = await fetch(STRAPI_INSTANCE + "/graphql", { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "omit", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify({ query }), + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + console.error("Fetch error:", error); + throw error; + } + } + + (async () => { + try { + const teamMembersData = await fetchTeamMembersJSON(); + + const teamMembersFromStrapi = teamMembersData.data.teamMembers.data.map( + (teamMember) => { + const heLocalization = teamMember.attributes.localizations.data[0]; + + return { + isTeamBoardMember: teamMember.attributes.isTeamBoardMember, + teamMemberImage: + teamMember.attributes.teamMemberImage?.data?.attributes?.url, + teamMemberDetails: { + teamName: { + en: teamMember.attributes.teamName, + he: heLocalization.attributes.teamName, + }, + teamTitle: { + en: teamMember.attributes.teamTitle, + he: heLocalization.attributes.teamTitle, + }, + }, + }; + } + ); + + const [ordinaryTeamMembers, teamBoardMembers] = partition( + teamMembersFromStrapi, + (teamMember) => !teamMember.isTeamBoardMember + ); + + setChildren(document.querySelector("section.main-text.team-members"), [ + ...TeamMembers(ordinaryTeamMembers.sort(byLastName)), + ...Placeholders(ordinaryTeamMembers.length, "teamMember"), + ]); + + setChildren(document.querySelector("section.main-text.board-members"), [ + ...BoardMembers(teamBoardMembers), + ...Placeholders(teamBoardMembers.length, "teamBoardMember"), + ]); + } catch (error) { + setChildren(document.querySelector("div.row.static-text"), el("h1", "Error: Sefaria's CMS cannot be reached")); + console.error(error); + } + })(); +} else { + setChildren(document.querySelector("div.row.static-text"), el("h1", "Error: Sefaria's CMS cannot be reached")); +} diff --git a/templates/static/team.html b/templates/static/team.html new file mode 100644 index 0000000000..009431411b --- /dev/null +++ b/templates/static/team.html @@ -0,0 +1,49 @@ + +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{% trans "The Sefaria Team" %}{% endblock %} + +{% block description %}{% trans "Sefaria's team is working to create the Jewish future." %}{% endblock %} + +{% block content %} + <script src="https://www.unpkg.com/redom@3.29.1/dist/redom.min.js"></script> + + <main id="teamPage" class="container static sans-serif"> + <div class="inner"> + <header> + <h1> + <span class="int-en serif">Team</span> + <span class="int-he">צוות ספריא</span> + </h1> + </header> + <div class="row static-text"> + <!-- Team members will be inserted through RE:DOM --> + <section class="main-text team-members"></section> + <header> + <h2> + <span class="int-en">BOARD OF DIRECTORS</span> + <span class="int-he">מועצת המנהלים</span> + </h2> + </header> + <!-- Board members will be inserted through RE:DOM --> + <section class="main-text board-members"></section> + + <script src="static/js/strapi_helpers/team_page.js" defer></script> + + <section id="teamContact"> + <div> + <span class="int-en">For general inquiries, please write to <a href="mailto:hello@sefaria.org">hello@sefaria.org</a>.</span> + <span class="int-he">לשאלות ובקשות כלליות, אנא צרו קשר בכתובת הדוא"ל <a href="mailto:hello@sefaria.org">hello@sefaria.org</a>.</span> + </div> + <div> + <span class="int-en">If you are interested in supporting Sefaria, please write to <a href="mailto:donate@sefaria.org">donate@sefaria.org</a>.</span> + <span class="int-he">אם הנכם מעוניינים לתמוך בספריא, אנא צרו קשר בכתובת הדוא"ל <a href="mailto:donate@sefaria.org">donate@sefaria.org</a>.</span> + </div> + </section> + + </div> + </div> + </main> + +{% endblock %} From 5604e1f916450249eee12db0c7d4760f9a971e32 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 30 Oct 2023 12:31:35 +0200 Subject: [PATCH 090/260] refactor(text api): rename isBaseText2 to isPrimary. --- sefaria/model/text.py | 2 +- sefaria/model/text_manager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 85221fd61a..f4bd284960 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1390,7 +1390,7 @@ class Version(AbstractTextRecord, abst.AbstractMongoRecord, AbstractSchemaConten "actualLanguage", "isBaseText", 'isSource', - 'isBaseText2', #temp + 'isPrimary', 'direction', # 1 for rtl, 2 for ltr ] diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 66412d597c..c0ecffc039 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -56,7 +56,7 @@ def _append_version(self, version): def _append_required_versions(self, lang: str, vtitle: str) -> None: if lang == self.BASE: - lang_condition = lambda v: getattr(v, 'isBaseText', False) + lang_condition = lambda v: getattr(v, 'isPrimary', False) elif lang == self.SOURCE: lang_condition = lambda v: getattr(v, 'isSource', False) elif lang == self.TRANSLATION: From 08cdbadd80cb425af3b30f93916e43a191b89b8a Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 30 Oct 2023 14:22:24 +0200 Subject: [PATCH 091/260] fix(text api): fix condition for adding a version. --- sefaria/model/text_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index c0ecffc039..433f3be5c3 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -72,7 +72,7 @@ def _append_required_versions(self, lang: str, vtitle: str) -> None: if vtitle != self.ALL and versions: versions = [max(versions, key=lambda v: getattr(v, 'priority', 0))] for version in versions: - if all(version.actualLanguage != v['actualLanguage'] and version.versionTitle != v['versionTitle'] for v in self.return_obj['versions']): + if all(version.actualLanguage != v['actualLanguage'] or version.versionTitle != v['versionTitle'] for v in self.return_obj['versions']): #do not return the same version even if included in two different version params self._append_version(version) if not versions: From 6c42f3c5488385b55f8f2ac05800525771516c5d Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Tue, 31 Oct 2023 01:41:27 -0400 Subject: [PATCH 092/260] feat(strapi-cms): Rewrite Team page to use a StaticPages component --- static/js/ReaderApp.jsx | 6 +- static/js/StaticPages.jsx | 261 ++++++++++++++++++++++++++++++++++++- templates/static/team.html | 25 ++-- 3 files changed, 275 insertions(+), 17 deletions(-) diff --git a/static/js/ReaderApp.jsx b/static/js/ReaderApp.jsx index 3dd75d07eb..0f779a6f18 100644 --- a/static/js/ReaderApp.jsx +++ b/static/js/ReaderApp.jsx @@ -21,7 +21,8 @@ import { EducatorsPage, RabbisPage, DonatePage, - WordByWordPage + WordByWordPage, + TeamMembersPage } from './StaticPages'; import { SignUpModal, @@ -2266,5 +2267,6 @@ export { EducatorsPage, RabbisPage, DonatePage, - WordByWordPage + WordByWordPage, + TeamMembersPage }; diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 4b2bc394d0..e90176b3d1 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -1,4 +1,4 @@ -import React, {useState, useRef} from 'react'; +import React, {useState, useRef, useEffect} from 'react'; import { SimpleInterfaceBlock, NewsletterSignUpForm, @@ -2636,6 +2636,262 @@ const StaticHR = () => const ConditionalLink = ({ link, children }) => link ? <a href={link} target="_blank">{children}</a> : children; +/* +* Team Page +*/ + +const byLastName = (a, b) => { + const lastNameA = a.teamMemberDetails.teamName.en.split(" ").pop(); + const lastNameB = b.teamMemberDetails.teamName.en.split(" ").pop(); + return lastNameA.localeCompare(lastNameB); +}; + +const partition = (arr, prop) => + arr.reduce( + (accumulator, currentValue) => { + accumulator[prop(currentValue) ? 0 : 1].push(currentValue); + return accumulator; + }, + [[], []] + ); + +const LocalizedText = ({ text }) => ( + <> + <span className="int-en">{text.en}</span> + <span className="int-he">{text.he}</span> + </> +); + +const TeamTitle = ({ teamTitle }) => ( + <div className="teamTitle"> + <LocalizedText text={teamTitle} /> + </div> +); + +const TeamName = ({ teamName }) => ( + <div className="teamName"> + <LocalizedText text={teamName} /> + </div> +); + +const TeamMemberDetails = ({ teamMemberDetails }) => ( + <div className="teamMemberDetails"> + <TeamName teamName={teamMemberDetails.teamName} /> + <TeamTitle teamTitle={teamMemberDetails.teamTitle} /> + </div> +); + +const TeamMemberImage = ({ teamMember }) => ( + <div className="teamMemberImage"> + <img + src={teamMember.teamMemberImage} + alt={`Headshot of ${teamMember.teamMemberDetails.teamName.en}`} + /> + </div> +); + +const TeamMember = ({ teamMember }) => ( + <div className="teamMember"> + <TeamMemberImage teamMember={teamMember} /> + <TeamMemberDetails teamMemberDetails={teamMember.teamMemberDetails} /> + </div> +); + +const TeamMembers = ({ teamMembers }) => ( + <> + {teamMembers.map((teamMember) => ( + <TeamMember key={teamMember.id} teamMember={teamMember} /> + ))} + </> +); + +const Placeholders = ({ teamMembersCount, cls }) => { + const placeholdersCount = + 3 - (teamMembersCount - 3 * Math.floor(teamMembersCount / 3)); + return ( + <> + {Array.from({ length: placeholdersCount }, (_, index) => ( + <div key={index} className={`${cls} placeholder`} /> + ))} + </> + ); +}; + +const BoardMember = ({ boardMember }) => ( + <div className="teamBoardMember"> + <TeamMemberDetails teamMemberDetails={boardMember.teamMemberDetails} /> + </div> +); + +const BoardMembers = ({ boardMembers }) => { + let chairmanBoardMember; + const chairmanIndex = boardMembers.findIndex( + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === "chairman" + ); + if (chairmanIndex !== -1) { + chairmanBoardMember = boardMembers.splice(chairmanIndex, 1); + } + const [cofounderBoardMembers, regularBoardMembers] = partition( + boardMembers, + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === "co-founder" + ); + + return ( + <> + {chairmanBoardMember && ( + <BoardMember boardMember={chairmanBoardMember[0]} /> + )} + {cofounderBoardMembers.map((boardMember) => ( + <BoardMember key={boardMember.id} boardMember={boardMember} /> + ))} + {regularBoardMembers.sort(byLastName).map((boardMember) => ( + <BoardMember key={boardMember.id} boardMember={boardMember} /> + ))} + </> + ); +}; + +const TeamMembersPage = () => { + const [ordinaryTeamMembers, setOrdinaryTeamMembers] = useState([]); + const [teamBoardMembers, setTeamBoardMembers] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchTeamMembersJSON = async () => { + const query = ` + query { + teamMembers(pagination: { limit: -1 }) { + data { + id + attributes { + teamName + teamTitle + isTeamBoardMember + teamMemberImage { + data { + attributes { + url + } + } + } + localizations { + data { + attributes { + locale + teamName + teamTitle + } + } + } + } + } + } + } + `; + try { + const response = await fetch(STRAPI_INSTANCE + "/graphql", { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "omit", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify({ query }), + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + throw error; + } + }; + + const loadTeamMembers = async () => { + if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { + try { + const teamMembersData = await fetchTeamMembersJSON(); + + const teamMembersFromStrapi = + teamMembersData.data.teamMembers.data.map((teamMember) => { + const heLocalization = + teamMember.attributes.localizations.data[0]; + + return { + id: teamMember.id, + isTeamBoardMember: teamMember.attributes.isTeamBoardMember, + teamMemberImage: + teamMember.attributes.teamMemberImage?.data?.attributes?.url, + teamMemberDetails: { + teamName: { + en: teamMember.attributes.teamName, + he: heLocalization.attributes.teamName, + }, + teamTitle: { + en: teamMember.attributes.teamTitle, + he: heLocalization.attributes.teamTitle, + }, + }, + }; + }); + + const [ordinaryMembers, boardMembers] = partition( + teamMembersFromStrapi, + (teamMember) => !teamMember.isTeamBoardMember + ); + + setOrdinaryTeamMembers(ordinaryMembers); + setTeamBoardMembers(boardMembers); + } catch (error) { + console.error("Fetch error:", error); + setError("Error: Sefaria's CMS cannot be reached"); + } + } else { + setError("Error: Sefaria's CMS cannot be reached"); + } + }; + + loadTeamMembers(); + }, []); + + return ( + <div> + {error ? ( + <h1>{error}</h1> + ) : ( + <> + <section className="main-text team-members"> + <TeamMembers teamMembers={ordinaryTeamMembers.sort(byLastName)} /> + <Placeholders + teamMembersCount={ordinaryTeamMembers.length} + cls="teamMember" + /> + </section> + <header> + <h2> + <span class="int-en">BOARD OF DIRECTORS</span> + <span class="int-he">מועצת המנהלים</span> + </h2> + </header> + <section className="main-text board-members"> + <BoardMembers boardMembers={teamBoardMembers} /> + <Placeholders + teamMembersCount={teamBoardMembers.length} + cls="teamBoardMember" + /> + </section> + </> + )} + </div> + ); +}; + export { RemoteLearningPage, @@ -2648,5 +2904,6 @@ export { EducatorsPage, RabbisPage, DonatePage, - WordByWordPage + WordByWordPage, + TeamMembersPage } diff --git a/templates/static/team.html b/templates/static/team.html index 009431411b..bb70829b27 100644 --- a/templates/static/team.html +++ b/templates/static/team.html @@ -6,8 +6,16 @@ {% block description %}{% trans "Sefaria's team is working to create the Jewish future." %}{% endblock %} +{% block js %} +<script> + {% autoescape off %} + DJANGO_VARS.containerId = 'teamMembersPageContent'; + DJANGO_VARS.reactComponentName = 'TeamMembersPage'; + {% endautoescape %} +</script> +{% endblock %} + {% block content %} - <script src="https://www.unpkg.com/redom@3.29.1/dist/redom.min.js"></script> <main id="teamPage" class="container static sans-serif"> <div class="inner"> @@ -18,18 +26,9 @@ <h1> </h1> </header> <div class="row static-text"> - <!-- Team members will be inserted through RE:DOM --> - <section class="main-text team-members"></section> - <header> - <h2> - <span class="int-en">BOARD OF DIRECTORS</span> - <span class="int-he">מועצת המנהלים</span> - </h2> - </header> - <!-- Board members will be inserted through RE:DOM --> - <section class="main-text board-members"></section> - - <script src="static/js/strapi_helpers/team_page.js" defer></script> + <div id="teamMembersPageContent"> + <h1 id="appLoading">Loading...</h1> + </div> <section id="teamContact"> <div> From 73e4a2dabe360b4cfacfeb418c62275bb943ddfb Mon Sep 17 00:00:00 2001 From: Lev Israel <eliezer.israel@gmail.com> Date: Tue, 31 Oct 2023 13:11:35 +0200 Subject: [PATCH 093/260] fix: Performance improvement of Ref.contains If `other` Ref is a whole book, and `self` begins anywhere after first segment, `contains` is trivially false and we can avoid expensive code. --- sefaria/model/tests/ref_test.py | 11 +++++++++++ sefaria/model/text.py | 7 +++++-- sefaria/model/text_manager.py | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/sefaria/model/tests/ref_test.py b/sefaria/model/tests/ref_test.py index 4bc8592d90..bb593f9f21 100644 --- a/sefaria/model/tests/ref_test.py +++ b/sefaria/model/tests/ref_test.py @@ -744,6 +744,17 @@ def test_contains(self): assert not Ref("Exodus 6:2").contains(Ref("Exodus")) assert not Ref("Exodus 6:2-12").contains(Ref("Exodus")) + assert Ref("Leviticus").contains(Ref("Leviticus 1:1-27.34")) + assert Ref("Leviticus").contains(Ref("Leviticus 1-27")) + assert Ref("Leviticus 1:1-27.34").contains(Ref("Leviticus")) + assert not Ref("Leviticus 1:1-27.30").contains(Ref("Leviticus")) + assert not Ref("Leviticus 1:2-27.30").contains(Ref("Leviticus")) + assert not Ref("Leviticus 2:2-27.30").contains(Ref("Leviticus")) + + # These fail, and always did + # assert not Ref("Leviticus").contains(Ref("Leviticus 1:1-27.35")) + # assert not Ref("Leviticus").contains(Ref("Leviticus 1-28")) + assert Ref("Rashi on Genesis 5:10-20").contains(Ref("Rashi on Genesis 5:18-20")) assert not Ref("Rashi on Genesis 5:10-20").contains(Ref("Rashi on Genesis 5:21-25")) assert not Ref("Rashi on Genesis 5:10-20").contains(Ref("Rashi on Genesis 5:15-25")) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index ba814657e0..c5cddf0bf6 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -4230,8 +4230,11 @@ def contains(self, other): if not self.index_node == other.index_node: return self.index_node.is_ancestor_of(other.index_node) - # If other is less specific than self, we need to get its true extent - if len(self.sections) > len(other.sections): + if len(self.sections) > len(other.sections): # other is less specific than self + if len(other.sections) == 0: # other is a whole book + if any([x != 1 for x in self.sections]): # self is not a whole book + return False # performance optimization to avoid call to as_ranged_segment_ref + # we need to get the true extent of other other = other.as_ranged_segment_ref() smallest_section_len = min([len(self.sections), len(other.sections)]) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 433f3be5c3..85640548aa 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -6,6 +6,7 @@ from sefaria.utils.hebrew import hebrew_term from typing import List + class TextManager: ALL = 'all' BASE = 'base' From 42d0bfa31757eb211ebd1a184307975b0c94ed6f Mon Sep 17 00:00:00 2001 From: Lev Israel <eliezer.israel@gmail.com> Date: Tue, 31 Oct 2023 16:59:31 +0200 Subject: [PATCH 094/260] fix: Add test for Ref.contains --- sefaria/model/tests/ref_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sefaria/model/tests/ref_test.py b/sefaria/model/tests/ref_test.py index bb593f9f21..b769f5dd87 100644 --- a/sefaria/model/tests/ref_test.py +++ b/sefaria/model/tests/ref_test.py @@ -744,6 +744,7 @@ def test_contains(self): assert not Ref("Exodus 6:2").contains(Ref("Exodus")) assert not Ref("Exodus 6:2-12").contains(Ref("Exodus")) + assert Ref("Leviticus").contains(Ref("Leviticus")) assert Ref("Leviticus").contains(Ref("Leviticus 1:1-27.34")) assert Ref("Leviticus").contains(Ref("Leviticus 1-27")) assert Ref("Leviticus 1:1-27.34").contains(Ref("Leviticus")) From 9223f0a5dccac52e69bbfab4820f36fbb0850c27 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Tue, 31 Oct 2023 12:28:56 -0400 Subject: [PATCH 095/260] Change team member components to use InterfaceText and reformat to match rest of source file --- static/js/StaticPages.jsx | 388 +++++++++++++++++++------------------- 1 file changed, 195 insertions(+), 193 deletions(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index e90176b3d1..7d0af1e4d2 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -1,4 +1,4 @@ -import React, {useState, useRef, useEffect} from 'react'; +import React, {useState, useRef, useEffect, memo} from 'react'; import { SimpleInterfaceBlock, NewsletterSignUpForm, @@ -2641,126 +2641,121 @@ const ConditionalLink = ({ link, children }) => */ const byLastName = (a, b) => { - const lastNameA = a.teamMemberDetails.teamName.en.split(" ").pop(); - const lastNameB = b.teamMemberDetails.teamName.en.split(" ").pop(); - return lastNameA.localeCompare(lastNameB); + const lastNameA = a.teamMemberDetails.teamName.en.split(" ").pop(); + const lastNameB = b.teamMemberDetails.teamName.en.split(" ").pop(); + return lastNameA.localeCompare(lastNameB); }; const partition = (arr, prop) => - arr.reduce( - (accumulator, currentValue) => { - accumulator[prop(currentValue) ? 0 : 1].push(currentValue); - return accumulator; - }, - [[], []] - ); - -const LocalizedText = ({ text }) => ( - <> - <span className="int-en">{text.en}</span> - <span className="int-he">{text.he}</span> - </> -); + arr.reduce( + (accumulator, currentValue) => { + accumulator[prop(currentValue) ? 0 : 1].push(currentValue); + return accumulator; + }, + [[], []] + ); const TeamTitle = ({ teamTitle }) => ( - <div className="teamTitle"> - <LocalizedText text={teamTitle} /> - </div> + <div className="teamTitle"> + <InterfaceText text={teamTitle} /> + </div> ); const TeamName = ({ teamName }) => ( - <div className="teamName"> - <LocalizedText text={teamName} /> - </div> + <div className="teamName"> + <InterfaceText text={teamName} /> + </div> ); const TeamMemberDetails = ({ teamMemberDetails }) => ( - <div className="teamMemberDetails"> - <TeamName teamName={teamMemberDetails.teamName} /> - <TeamTitle teamTitle={teamMemberDetails.teamTitle} /> - </div> + <div className="teamMemberDetails"> + <TeamName teamName={teamMemberDetails.teamName} /> + <TeamTitle teamTitle={teamMemberDetails.teamTitle} /> + </div> ); const TeamMemberImage = ({ teamMember }) => ( - <div className="teamMemberImage"> - <img - src={teamMember.teamMemberImage} - alt={`Headshot of ${teamMember.teamMemberDetails.teamName.en}`} - /> - </div> + <div className="teamMemberImage"> + <img + src={teamMember.teamMemberImage} + alt={`Headshot of ${teamMember.teamMemberDetails.teamName.en}`} + /> + </div> ); const TeamMember = ({ teamMember }) => ( - <div className="teamMember"> - <TeamMemberImage teamMember={teamMember} /> - <TeamMemberDetails teamMemberDetails={teamMember.teamMemberDetails} /> - </div> + <div className="teamMember"> + <TeamMemberImage teamMember={teamMember} /> + <TeamMemberDetails teamMemberDetails={teamMember.teamMemberDetails} /> + </div> ); const TeamMembers = ({ teamMembers }) => ( - <> - {teamMembers.map((teamMember) => ( - <TeamMember key={teamMember.id} teamMember={teamMember} /> - ))} - </> + <> + {teamMembers.map((teamMember) => ( + <TeamMember key={teamMember.id} teamMember={teamMember} /> + ))} + </> ); const Placeholders = ({ teamMembersCount, cls }) => { - const placeholdersCount = - 3 - (teamMembersCount - 3 * Math.floor(teamMembersCount / 3)); - return ( - <> - {Array.from({ length: placeholdersCount }, (_, index) => ( - <div key={index} className={`${cls} placeholder`} /> - ))} - </> - ); + const placeholdersCount = + 3 - (teamMembersCount - 3 * Math.floor(teamMembersCount / 3)); + return ( + <> + {Array.from({ length: placeholdersCount }, (_, index) => ( + <div key={index} className={`${cls} placeholder`} /> + ))} + </> + ); }; const BoardMember = ({ boardMember }) => ( - <div className="teamBoardMember"> - <TeamMemberDetails teamMemberDetails={boardMember.teamMemberDetails} /> - </div> + <div className="teamBoardMember"> + <TeamMemberDetails teamMemberDetails={boardMember.teamMemberDetails} /> + </div> ); const BoardMembers = ({ boardMembers }) => { - let chairmanBoardMember; - const chairmanIndex = boardMembers.findIndex( - (boardMember) => - boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === "chairman" - ); - if (chairmanIndex !== -1) { - chairmanBoardMember = boardMembers.splice(chairmanIndex, 1); - } - const [cofounderBoardMembers, regularBoardMembers] = partition( - boardMembers, - (boardMember) => - boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === "co-founder" - ); - - return ( - <> - {chairmanBoardMember && ( - <BoardMember boardMember={chairmanBoardMember[0]} /> - )} - {cofounderBoardMembers.map((boardMember) => ( - <BoardMember key={boardMember.id} boardMember={boardMember} /> - ))} - {regularBoardMembers.sort(byLastName).map((boardMember) => ( - <BoardMember key={boardMember.id} boardMember={boardMember} /> - ))} - </> - ); + let chairmanBoardMember; + const chairmanIndex = boardMembers.findIndex( + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === + "chairman" + ); + if (chairmanIndex !== -1) { + chairmanBoardMember = boardMembers.splice(chairmanIndex, 1); + } + const [cofounderBoardMembers, regularBoardMembers] = partition( + boardMembers, + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === + "co-founder" + ); + + return ( + <> + {chairmanBoardMember && ( + <BoardMember boardMember={chairmanBoardMember[0]} /> + )} + {cofounderBoardMembers.map((boardMember) => ( + <BoardMember key={boardMember.id} boardMember={boardMember} /> + ))} + {regularBoardMembers.sort(byLastName).map((boardMember) => ( + <BoardMember key={boardMember.id} boardMember={boardMember} /> + ))} + </> + ); }; -const TeamMembersPage = () => { - const [ordinaryTeamMembers, setOrdinaryTeamMembers] = useState([]); - const [teamBoardMembers, setTeamBoardMembers] = useState([]); - const [error, setError] = useState(null); +const TeamMembersPage = memo(() => { + const [ordinaryTeamMembers, setOrdinaryTeamMembers] = useState([]); + const [teamBoardMembers, setTeamBoardMembers] = useState([]); + const [error, setError] = useState(null); - useEffect(() => { - const fetchTeamMembersJSON = async () => { - const query = ` + useEffect(() => { + const fetchTeamMembersJSON = async () => { + const query = ` query { teamMembers(pagination: { limit: -1 }) { data { @@ -2790,108 +2785,115 @@ const TeamMembersPage = () => { } } `; - try { - const response = await fetch(STRAPI_INSTANCE + "/graphql", { - method: "POST", - mode: "cors", - cache: "no-cache", - credentials: "omit", - headers: { - "Content-Type": "application/json", - }, - redirect: "follow", - referrerPolicy: "no-referrer", - body: JSON.stringify({ query }), - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.statusText}`); - } - const data = await response.json(); - return data; - } catch (error) { - throw error; - } - }; - - const loadTeamMembers = async () => { - if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { - try { - const teamMembersData = await fetchTeamMembersJSON(); - - const teamMembersFromStrapi = - teamMembersData.data.teamMembers.data.map((teamMember) => { - const heLocalization = - teamMember.attributes.localizations.data[0]; - - return { - id: teamMember.id, - isTeamBoardMember: teamMember.attributes.isTeamBoardMember, - teamMemberImage: - teamMember.attributes.teamMemberImage?.data?.attributes?.url, - teamMemberDetails: { - teamName: { - en: teamMember.attributes.teamName, - he: heLocalization.attributes.teamName, - }, - teamTitle: { - en: teamMember.attributes.teamTitle, - he: heLocalization.attributes.teamTitle, - }, - }, - }; - }); - - const [ordinaryMembers, boardMembers] = partition( - teamMembersFromStrapi, - (teamMember) => !teamMember.isTeamBoardMember - ); - - setOrdinaryTeamMembers(ordinaryMembers); - setTeamBoardMembers(boardMembers); - } catch (error) { - console.error("Fetch error:", error); - setError("Error: Sefaria's CMS cannot be reached"); - } - } else { - setError("Error: Sefaria's CMS cannot be reached"); - } - }; - - loadTeamMembers(); - }, []); - - return ( - <div> - {error ? ( - <h1>{error}</h1> - ) : ( - <> - <section className="main-text team-members"> - <TeamMembers teamMembers={ordinaryTeamMembers.sort(byLastName)} /> - <Placeholders - teamMembersCount={ordinaryTeamMembers.length} - cls="teamMember" - /> - </section> - <header> - <h2> - <span class="int-en">BOARD OF DIRECTORS</span> - <span class="int-he">מועצת המנהלים</span> - </h2> - </header> - <section className="main-text board-members"> - <BoardMembers boardMembers={teamBoardMembers} /> - <Placeholders - teamMembersCount={teamBoardMembers.length} - cls="teamBoardMember" - /> - </section> - </> - )} - </div> - ); -}; - + try { + const response = await fetch(STRAPI_INSTANCE + "/graphql", { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "omit", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify({ query }), + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + throw error; + } + }; + + const loadTeamMembers = async () => { + if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { + try { + const teamMembersData = await fetchTeamMembersJSON(); + + const teamMembersFromStrapi = + teamMembersData.data.teamMembers.data.map( + (teamMember) => { + const heLocalization = + teamMember.attributes.localizations.data[0]; + + return { + id: teamMember.id, + isTeamBoardMember: + teamMember.attributes.isTeamBoardMember, + teamMemberImage: + teamMember.attributes.teamMemberImage + ?.data?.attributes?.url, + teamMemberDetails: { + teamName: { + en: teamMember.attributes.teamName, + he: heLocalization.attributes + .teamName, + }, + teamTitle: { + en: teamMember.attributes.teamTitle, + he: heLocalization.attributes + .teamTitle, + }, + }, + }; + } + ); + + const [ordinaryMembers, boardMembers] = partition( + teamMembersFromStrapi, + (teamMember) => !teamMember.isTeamBoardMember + ); + + setOrdinaryTeamMembers(ordinaryMembers); + setTeamBoardMembers(boardMembers); + } catch (error) { + console.error("Fetch error:", error); + setError("Error: Sefaria's CMS cannot be reached"); + } + } else { + setError("Error: Sefaria's CMS cannot be reached"); + } + }; + + loadTeamMembers(); + }, []); + + return ( + <div> + {error ? ( + <h1>{error}</h1> + ) : ( + <> + <section className="main-text team-members"> + <TeamMembers + teamMembers={ordinaryTeamMembers.sort(byLastName)} + /> + <Placeholders + teamMembersCount={ordinaryTeamMembers.length} + cls="teamMember" + /> + </section> + <header> + <h2> + <span class="int-en">BOARD OF DIRECTORS</span> + <span class="int-he">מועצת המנהלים</span> + </h2> + </header> + <section className="main-text board-members"> + <BoardMembers boardMembers={teamBoardMembers} /> + <Placeholders + teamMembersCount={teamBoardMembers.length} + cls="teamBoardMember" + /> + </section> + </> + )} + </div> + ); +}); export { RemoteLearningPage, @@ -2905,5 +2907,5 @@ export { RabbisPage, DonatePage, WordByWordPage, - TeamMembersPage -} + TeamMembersPage, +}; From d5c5dfb40049bf3be54a5192a83619a380621803 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Tue, 31 Oct 2023 12:37:13 -0400 Subject: [PATCH 096/260] Remove unnecessary files --- static/js/strapi_helpers/team_page.js | 202 --------- templates/static/en/team.html | 627 -------------------------- templates/static/he/team.html | 624 ------------------------- 3 files changed, 1453 deletions(-) delete mode 100644 static/js/strapi_helpers/team_page.js delete mode 100644 templates/static/en/team.html delete mode 100644 templates/static/he/team.html diff --git a/static/js/strapi_helpers/team_page.js b/static/js/strapi_helpers/team_page.js deleted file mode 100644 index 6df692bfae..0000000000 --- a/static/js/strapi_helpers/team_page.js +++ /dev/null @@ -1,202 +0,0 @@ -const { el, mount, setChildren } = redom; - -const byLastName = (a, b) => { - const c = a.teamMemberDetails.teamName.en; - const d = b.teamMemberDetails.teamName.en; - const lastNameA = c.split(" ").pop(); - const lastNameB = d.split(" ").pop(); - return lastNameA.localeCompare(lastNameB); -}; - -const partition = (arr, prop) => - arr.reduce( - (accumulator, currentValue) => { - accumulator[prop(currentValue) ? 0 : 1].push(currentValue); - return accumulator; - }, - [[], []] - ); - -function LocalizedText(text) { - return [el("span.int-en", text.en), el("span.int-he", text.he)]; -} - -function TeamTitle(teamTitle) { - return el(".teamTitle", LocalizedText(teamTitle)); -} - -function TeamName(teamName) { - return el(".teamName", LocalizedText(teamName)); -} - -function TeamMemberDetails(teamMemberDetails) { - return el(".teamMemberDetails", [ - TeamName(teamMemberDetails.teamName), - TeamTitle(teamMemberDetails.teamTitle), - ]); -} - -function TeamMemberImage(teamMember) { - return el( - ".teamMemberImage", - el("img", { - src: teamMember.teamMemberImage, - alt: "Headshot of " + teamMember.teamMemberDetails.teamName.en, - }) - ); -} - -function TeamMember(teamMember) { - return el(".teamMember", [ - TeamMemberImage(teamMember), - TeamMemberDetails(teamMember.teamMemberDetails), - ]); -} - -function TeamMembers(teamMembers) { - return teamMembers.map((teamMember) => TeamMember(teamMember)); -} - -function Placeholders(teamMembersCount, cls) { - // Determine the number of empty spots to have as placeholders in the last row - placeholdersCount = 3 - (teamMembersCount - 3 * ~~(teamMembersCount / 3)); - return Array.from({ length: placeholdersCount }, () => - el("." + cls + ".placeholder") - ); -} - -function BoardMember(boardMember) { - return el( - ".teamBoardMember", - TeamMemberDetails(boardMember.teamMemberDetails) - ); -} - -function BoardMembers(boardMembers) { - // Separate out the chairman and co-founders for the correct ordering - let chairmanBoardMember; - let chairmanIndex = boardMembers.findIndex( - (boardMember) => - boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === "chairman" - ); - if (chairmanIndex !== -1) { - chairmanBoardMember = boardMembers.splice(chairmanIndex, 1); - } - const [cofounderBoardMembers, regularBoardMembers] = partition( - boardMembers, - (boardMember) => - boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === "co-founder" - ); - // Produce the nodes with the right order for the board members - // Chairman, Co-founders, rest of the board - return [ - ...(chairmanBoardMember ?? []), - ...(cofounderBoardMembers ?? []), - ...regularBoardMembers.sort(byLastName), - ].map((boardMember) => BoardMember(boardMember)); -} - -if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { - async function fetchTeamMembersJSON() { - const query = ` - query { - teamMembers(pagination: { limit: -1 }) { - data { - id - attributes { - teamName - teamTitle - isTeamBoardMember - teamMemberImage { - data { - attributes { - url - } - } - } - localizations { - data { - attributes { - locale - teamName - teamTitle - } - } - } - } - } - } - } - `; - try { - const response = await fetch(STRAPI_INSTANCE + "/graphql", { - method: "POST", - mode: "cors", - cache: "no-cache", - credentials: "omit", - headers: { - "Content-Type": "application/json", - }, - redirect: "follow", - referrerPolicy: "no-referrer", - body: JSON.stringify({ query }), - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.statusText}`); - } - const data = await response.json(); - return data; - } catch (error) { - console.error("Fetch error:", error); - throw error; - } - } - - (async () => { - try { - const teamMembersData = await fetchTeamMembersJSON(); - - const teamMembersFromStrapi = teamMembersData.data.teamMembers.data.map( - (teamMember) => { - const heLocalization = teamMember.attributes.localizations.data[0]; - - return { - isTeamBoardMember: teamMember.attributes.isTeamBoardMember, - teamMemberImage: - teamMember.attributes.teamMemberImage?.data?.attributes?.url, - teamMemberDetails: { - teamName: { - en: teamMember.attributes.teamName, - he: heLocalization.attributes.teamName, - }, - teamTitle: { - en: teamMember.attributes.teamTitle, - he: heLocalization.attributes.teamTitle, - }, - }, - }; - } - ); - - const [ordinaryTeamMembers, teamBoardMembers] = partition( - teamMembersFromStrapi, - (teamMember) => !teamMember.isTeamBoardMember - ); - - setChildren(document.querySelector("section.main-text.team-members"), [ - ...TeamMembers(ordinaryTeamMembers.sort(byLastName)), - ...Placeholders(ordinaryTeamMembers.length, "teamMember"), - ]); - - setChildren(document.querySelector("section.main-text.board-members"), [ - ...BoardMembers(teamBoardMembers), - ...Placeholders(teamBoardMembers.length, "teamBoardMember"), - ]); - } catch (error) { - setChildren(document.querySelector("div.row.static-text"), el("h1", "Error: Sefaria's CMS cannot be reached")); - console.error(error); - } - })(); -} else { - setChildren(document.querySelector("div.row.static-text"), el("h1", "Error: Sefaria's CMS cannot be reached")); -} diff --git a/templates/static/en/team.html b/templates/static/en/team.html deleted file mode 100644 index 7b94e3ca1c..0000000000 --- a/templates/static/en/team.html +++ /dev/null @@ -1,627 +0,0 @@ - -{% extends "base.html" %} -{% load i18n static %} - -{% block title %}{% trans "The Sefaria Team" %}{% endblock %} - -{% block description %}{% trans "Sefaria's team is working to create the Jewish future." %}{% endblock %} - -{% block content %} - <main id="teamPage" class="container static sans-serif"> - <div class="inner"> - <header> - <h1> - <span class="int-en serif">Team</span> - </h1> - </header> - <div class="row static-text"> - <section class="main-text team-members"> - <!-- - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/gabriel.png' %}" alt="Headshot of Gabriel Winer"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Gabriel Winer</span> - - </div> - <div class="teamTitle"> - <span class="int-en">Director of Product</span> - </div> - </div> - </div> - --> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/akiva.png' %}" alt="Headshot of Akiva Berger"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Akiva Berger</span> - </div> - <div class="teamTitle"> - <span class="int-en">Director of Engineering, Israel</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/rachel.png' %}" alt="Headshot of Rachel Buckman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Rachel Buckman</span> - </div> - <div class="teamTitle"> - <span class="int-en">Sr. Learning & Support Coordinator</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/skyler.png' %}" alt="Headshot"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Skyler Cohen</span> - </div> - <div class="teamTitle"> - <span class="int-en">Junior Software Engineer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/caitlyn.png' %}" alt="Headshot of Caitlyn Cushing"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Caitlyn Cushing</span> - </div> - <div class="teamTitle"> - <span class="int-en">Development Operations Associate</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/ephraim.png' %}" alt="Headshot of Ephraim Damboritz"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Ephraim Damboritz</span> - </div> - <div class="teamTitle"> - <span class="int-en">Sr. Engineering Specialist</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/sarit.png' %}" alt="Headshot of Sarit Dubin"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Sarit Dubin</span> - </div> - <div class="teamTitle"> - <span class="int-en">Senior Communications Manager</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/michael-f.png' %}" alt="Headshot of Michael Fankhauser"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Michael Fankhauser</span> - </div> - <div class="teamTitle"> - <span class="int-en">Senior Product Manager</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/olivia.png' %}" alt="Headshot of Olivia Gerber"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Olivia Gerber</span> - </div> - <div class="teamTitle"> - <span class="int-en">Marketing Associate</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/yishai.png' %}" alt="Headshot of Yishai Glasner"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Yishai Glasner</span> - </div> - <div class="teamTitle"> - <span class="int-en">Software Engineer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/hannah.png' %}" alt="Headshot of Hannah Goldberger"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Hannah Goldberger</span> - </div> - <div class="teamTitle"> - <span class="int-en">Director of Digital Fundraising</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/justin.png' %}" alt="Headshot of Justin Goldstein"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Justin Goldstein</span> - </div> - <div class="teamTitle"> - <span class="int-en">Learning Coordinator</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/talia.png' %}" alt="Headshot of Talia Graff"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Talia Graff</span> - </div> - <div class="teamTitle"> - <span class="int-en">Chief of Staff</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/rachel-g.png' %}" alt="Headshot of Rachel Grossman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Rachel Grossman</span> - </div> - <div class="teamTitle"> - <span class="int-en">Editorial Associate</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/tali.jpg' %}" alt="Headshot of Tali Herenstein"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Tali Herenstein</span> - </div> - <div class="teamTitle"> - <span class="int-en">Chief Strategy Officer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/lev.png' %}" alt="Headshot of Lev Israel"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Lev Israel</span> - </div> - <div class="teamTitle"> - <span class="int-en">Chief Product Officer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/steve.png' %}" alt="Headshot of Steve Kaplan"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Steve Kaplan</span> - </div> - <div class="teamTitle"> - <span class="int-en">Software Engineer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/sarah.png' %}" alt="Headshot of Sarah Kreitman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Sarah Kreitman</span> - </div> - <div class="teamTitle"> - <span class="int-en">Junior Software Engineer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/yonadav.png' %}" alt="Headshot of Yonadav Leibowitz"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Yonadav Leibowitz</span> - </div> - <div class="teamTitle"> - <span class="int-en">Junior Research Engineer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/annie.png' %}" alt="Headshot of Annie Lumerman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Annie Lumerman</span> - </div> - <div class="teamTitle"> - <span class="int-en">Chief Operating Officer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/Amanda_Minsky_Photo.png' %}" alt="Headshot of Amanda Minsky"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Amanda Minsky</span> - </div> - <div class="teamTitle"> - <span class="int-en">People & Operations Coordinator</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/francis.png' %}" alt="Headshot of Francis Nataf"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Francis Nataf</span> - </div> - <div class="teamTitle"> - <span class="int-en">Translation and Research Specialist</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/russel.png' %}" alt="Headshot of Russel Neiss"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Russel Neiss</span> - </div> - <div class="teamTitle"> - <span class="int-en">Product & Engineering Director</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/desiree.png' %}" alt="Headshot of Desiree Neissani"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Desiree Neissani</span> - </div> - <div class="teamTitle"> - <span class="int-en">Special Assistant, Office of the CEO</span> - </div> - </div> - </div> - - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/rebecca.png' %}" alt="Headshot of Rebecca Remisn"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Rebecca Remis</span> - </div> - <div class="teamTitle"> - <span class="int-en">Sr. People & Operations Manager</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/elise.png' %}" alt="Headshot of Elise Ringo"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Elise Ringo</span> - </div> - <div class="teamTitle"> - <span class="int-en">Database Administrator</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/shanee.png' %}" alt="Headshot of Shanee Rosen"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Shanee Rosen</span> - </div> - <div class="teamTitle"> - <span class="int-en">Sr. Software Engineer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/noah.png' %}" alt="Headshot of Noah Santacruz"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Noah Santacruz</span> - </div> - <div class="teamTitle"> - <span class="int-en">Sr. Engineering Manager</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/daniel.png' %}" alt="Headshot of Daniel Septimus"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Daniel Septimus</span> - </div> - <div class="teamTitle"> - <span class="int-en">Chief Executive Officer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/samantha.png' %}" alt="Headshot of Samantha Shokin"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Samantha Shokin</span> - </div> - <div class="teamTitle"> - <span class="int-en">Grants Coordinator</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/hadara.png' %}" alt="Headshot of Hadara Steinberg"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Hadara Steinberg</span> - </div> - <div class="teamTitle"> - <span class="int-en">Project Manager</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/israel.png' %}" alt="Headshot of Israel Tsadok"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Israel Tsadok</span> - </div> - <div class="teamTitle"> - <span class="int-en">Learning & Content Coordinator</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/chava.jpg' %}" alt="Headshot of Chava Tzemach"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Chava Tzemach</span> - </div> - <div class="teamTitle"> - <span class="int-en">Director of Marketing and Communications</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/shmuel.png' %}" alt="Headshot of Shmuel Weissman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Shmuel Weissman</span> - </div> - <div class="teamTitle"> - <span class="int-en">Manager of Text Acquisition and Text Quality</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/sara.png' %}" alt="Headshot of Sara Wolkenfeld"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Sara Wolkenfeld</span> - </div> - <div class="teamTitle"> - <span class="int-en">Chief Learning Officer</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/hedva.png' %}" alt="Headshot of Hedva Yechieli"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Hedva Yechieli</span> - </div> - <div class="teamTitle"> - <span class="int-en">Learning & Engagement Coordinator</span> - </div> - </div> - </div> - <div class="teamMember placeholder"></div> - <div class="teamMember placeholder"></div> - </section> - <header> - <h2> - <span class="int-en">BOARD OF DIRECTORS</span> - </h2> - </header> - <section class="main-text board-members"> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Samuel Moed</span> - </div> - <div class="teamTitle"> - <span class="int-en">Chairman</span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Joshua Foer</span> - </div> - <div class="teamTitle"> - <span class="int-en">Co-Founder</span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Brett Lockspeiser</span> - </div> - <div class="teamTitle"> - <span class="int-en">Co-Founder</span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Mo Koyfman</span> - </div> - <div class="teamTitle"> - <span class="int-en"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Elana Stein Hain</span> - </div> - <div class="teamTitle"> - <span class="int-en"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Jonathan Koschitzky</span> - </div> - <div class="teamTitle"> - <span class="int-en"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Joshua Kushner</span> - </div> - <div class="teamTitle"> - <span class="int-en"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Raanan Agus</span> - </div> - <div class="teamTitle"> - <span class="int-en"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Rona Sheramy</span> - </div> - <div class="teamTitle"> - <span class="int-en"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-en">Michael Englander</span> - </div> - <div class="teamTitle"> - <span class="int-en"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-en">Deborah Shapira</span> - </div> - <div class="teamTitle"> - <span class="int-en"></span> - </div> - </div> - </div> - <div class="teamBoardMember placeholder"></div> - </section> - - - <section id="teamContact"> - <div> - <span class="int-en">For general inquiries, please write to <a href="mailto:hello@sefaria.org">hello@sefaria.org</a>.</span> - </div> - <div> - <span class="int-en">If you are interested in supporting Sefaria, please write to <a href="mailto:donate@sefaria.org">donate@sefaria.org</a>.</span> - </div> - </section> - - </div> - </div> - </main> - -{% endblock %} diff --git a/templates/static/he/team.html b/templates/static/he/team.html deleted file mode 100644 index d9049132fa..0000000000 --- a/templates/static/he/team.html +++ /dev/null @@ -1,624 +0,0 @@ -{% extends "base.html" %} -{% load i18n static %} - -{% block title %}{% trans "The Sefaria Team" %}{% endblock %} - -{% block description %}{% trans "Sefaria's team is working to create the Jewish future." %}{% endblock %} - -{% block content %} - <main id="teamPage" class="container static"> - <div class="inner"> - <header> - <h1> - <span class="int-he">צוות ספריא</span> - </h1> - </header> - <div class="row static-text"> - <section class="main-text team-members"> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/rachel.png' %}" alt="Headshot of Rachel Buckman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">רחל בקמן</span> - </div> - <div class="teamTitle"> - <span class="int-he">רכזת לימוד ותמיכה בכירה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/akiva.png' %}" alt="Headshot of Akiva Berger"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">עקיבא ברגר</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהל צוות הנדסה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/hannah.png' %}" alt="Headshot of Hannah Goldberger"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">חנה גולדברגר</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת אגף דיגיטל לגיוס כספים ומענקים</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/justin.png' %}" alt="Headshot of Justin Goldstein"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">ג'סטין גולדשטיין</span> - </div> - <div class="teamTitle"> - <span class="int-he">רכז לענייני לימוד</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/yishai.png' %}" alt="Headshot of Yishai Glasner"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">ישי גלזנר</span> - </div> - <div class="teamTitle"> - <span class="int-he">מהנדס תוכנה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/olivia.png' %}" alt="Headshot of Rachel Grossman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">אוליביה גרבר</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת שיווק</span> - </div> - </div> - </div> - - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/rachel-g.png' %}" alt="Headshot of Rachel Grossman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">רחל גרוסמן</span> - </div> - <div class="teamTitle"> - <span class="int-he">רכזת עריכת תוכן</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/talia.png' %}" alt="Headshot of Talia Graff"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">טליה גרף</span> - </div> - <div class="teamTitle"> - <span class="int-he">ראש הסגל</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/sarit.png' %}" alt="Headshot of Sarit Dubin"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">שרית דובין</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת תקשורת בכירה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/ephraim.png' %}" alt="Headshot of Ephraim Damboritz"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">אפרים דמבוריץ</span> - </div> - <div class="teamTitle"> - <span class="int-he">מומחה הנדסה בכיר</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/tali.jpg' %}" alt="Headshot of Tali Herenstein"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">טלי הרנשטיין</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת אסטרטגיה בכירה</span> - </div> - </div> - </div> - <!-- - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/gabriel.png' %}" alt="Headshot of Gabriel Winer"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">גבריאל ווינר</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהל מוצר</span> - </div> - </div> - </div> - --> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/sara.png' %}" alt="Headshot of Sara Wolkenfeld"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">שרה וולקנפלד</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת חינוך והוראה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/shmuel.png' %}" alt="Headshot of Shmuel Weissman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">שמואל וייסמן</span> - </div> - <div class="teamTitle"> - <span class="int-he">אחראי רכש טקסטים ואיכות</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/hedva.png' %}" alt="Headshot of Hedva Yechieli"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">חדוה יחיאלי</span> - </div> - <div class="teamTitle"> - <span class="int-he">רכזת חינוך ומעורבות</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/lev.png' %}" alt="Headshot of Lev Israel"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">לב ישראל</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהל מוצר ראשי</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/skyler.png' %}" alt="Headshot"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">זלמן כהן</span> - </div> - <div class="teamTitle"> - <span class="int-he">מהנדס תוכנה זוטר</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/yonadav.png' %}" alt="Headshot of Yonadav Leibowitz"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">יונדב ליבוביץ</span> - </div> - <div class="teamTitle"> - <span class="int-he">מהנדס מחקר זוטר</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/annie.png' %}" alt="Headshot of Annie Lumerman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">אנני לומרמן</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת תפעול ראשית</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/Amanda_Minsky_Photo.png' %}" alt="Headshot of Amanda Minsky"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">אמנדה מינסקי</span> - </div> - <div class="teamTitle"> - <span class="int-he">רכזת אנשים ותפעול</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/francis.png' %}" alt="Headshot of Francis Nataf"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">יעקב (פרנסיס) נטף</span> - </div> - <div class="teamTitle"> - <span class="int-he">מומחה תרגום ומחקר</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/russel.png' %}" alt="Headshot of Russel Neiss"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">רזיאל ניס</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהל מוצר והנדסה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/desiree.png' %}" alt="Headshot of Desiree Neissani"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">שרה ניסני</span> - </div> - <div class="teamTitle"> - <span class="int-he">עוזר מיוחד, משרד המנכ"ל</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/noah.png' %}" alt="Headshot of Noah Santacruz"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">נח סנטקרוז</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהל הנדסה בכיר</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/daniel.png' %}" alt="Headshot of Daniel Septimus"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">דניאל ספטימוס</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהל כללי</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/michael-f.png' %}" alt="Headshot of Michael Fankhauser"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">מיכאל פאנקהאוזר</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהל מוצר בכיר</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/israel.png' %}" alt="Headshot of Israel Tsadok"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">ישראל צדוק</span> - </div> - <div class="teamTitle"> - <span class="int-he">רכז חינוך ותוכן</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/chava.jpg' %}" alt="Headshot of Chava Tzemach"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">חוה צמח</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת אגף תקשורת ושיווק</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/caitlyn.png' %}" alt="Headshot of Caitlyn Cushing"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">קייטלין קושינג</span> - </div> - <div class="teamTitle"> - <span class="int-he">רכזת פיתוח משאבים</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/steve.png' %}" alt="Headshot of Steve Kaplan"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">סטיב קפלן</span> - </div> - <div class="teamTitle"> - <span class="int-he">מהנדס תוכנה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/sarah.png' %}" alt="Headshot of Sarah Kreitman"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">שרה קרייטמן</span> - </div> - <div class="teamTitle"> - <span class="int-he">מהנדסת תוכנה זוטרה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/shanee.png' %}" alt="Headshot of Shanee Rosen"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">שני רוזן</span> - </div> - <div class="teamTitle"> - <span class="int-he">מהנדסת תוכנה בכירה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/elise.png' %}" alt="Headshot of Elise Ringo"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">יעל רינגו</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת מאגר נתונים</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/rebecca.png' %}" alt="Headshot of Rebecca Remis"> - </div> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">רבקה רמיס</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת אנשים ותפעול בכירה</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/samantha.png' %}" alt="Headshot of Samantha Shokin"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">סמנתה שוקין</span> - </div> - <div class="teamTitle"> - <span class="int-he">רכזת גרנטים</span> - </div> - </div> - </div> - <div class="teamMember"> - <div class="teamMemberImage"> - <img src="{% static 'img/headshots/hadara.png' %}" alt="Headshot of Hadara Steinberg"> - </div> - <div class="teamMemberDetails"> - <div class="teamName"> - <span class="int-he">הדרה שטיינברג</span> - </div> - <div class="teamTitle"> - <span class="int-he">מנהלת פרוייקטים</span> - </div> - </div> - </div> - <div class="teamMember placeholder"></div> - <div class="teamMember placeholder"></div> - </section> - <header> - <h2> - <span class="int-he">מועצת המנהלים</span> - </h2> - </header> - <section class="main-text board-members"> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">שמואל מועד</span> - </div> - <div class="teamTitle"> - <span class="int-he">יושב ראש</span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">ג'ושוע פוייר</span> - </div> - <div class="teamTitle"> - <span class="int-he">מייסד-שותף</span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">ברט לקספיזר</span> - </div> - <div class="teamTitle"> - <span class="int-he">מייסד-שותף</span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">מו קויפמן</span> - </div> - <div class="teamTitle"> - <span class="int-he"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">אלנה שטיין היין</span> - </div> - <div class="teamTitle"> - <span class="int-he"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">יונתן קושיצקי</span> - </div> - <div class="teamTitle"> - <span class="int-he"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">ג'ושוע קושנר</span> - </div> - <div class="teamTitle"> - <span class="int-he"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">רענן אגוס</span> - </div> - <div class="teamTitle"> - <span class="int-he"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">רונה שרמי</span> - </div> - <div class="teamTitle"> - <span class="int-he"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">מיכאל ענגלענדער</span> - </div> - <div class="teamTitle"> - <span class="int-he"></span> - </div> - </div> - </div> - <div class="teamBoardMember"> - <div class="teamMemberDetails"> - <div class="teamName sans"> - <span class="int-he">דברה שפירא</span> - </div> - <div class="teamTitle"> - <span class="int-he"></span> - </div> - </div> - </div> - <div class="teamBoardMember placeholder"></div> - </section> - - - <section id="teamContact"> - <div> - <span class="int-he">לשאלות ובקשות כלליות, אנא צרו קשר בכתובת הדוא"ל <a href="mailto:hello@sefaria.org">hello@sefaria.org</a>.</span> - </div> - <div> - <span class="int-he">אם הנכם מעוניינים לתמוך בספריא, אנא צרו קשר בכתובת הדוא"ל <a href="mailto:donate@sefaria.org">donate@sefaria.org</a>.</span> - </div> - </section> - </div> - </div> - </main> - -{% endblock %} From 1b350682fcefaf4b57d7849f3deb9daddd3bfdc5 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Wed, 1 Nov 2023 14:46:20 -0400 Subject: [PATCH 097/260] feat(strapi-cms): Rewrite Jobs page to use StaticPages component and to be editable through Strapi --- static/css/static.css | 5 + static/js/ReaderApp.jsx | 6 +- static/js/StaticPages.jsx | 203 ++++++++++++++++++++++++++++++++++++- templates/static/jobs.html | 117 ++------------------- 4 files changed, 221 insertions(+), 110 deletions(-) diff --git a/static/css/static.css b/static/css/static.css index eec62cdfa3..02eded1691 100644 --- a/static/css/static.css +++ b/static/css/static.css @@ -1266,6 +1266,11 @@ p.registration-links a:hover{ margin-bottom: 40px; font-weight: bold; } + +#jobsPage .nothing a { + display: inline-block; +} + #jobsPage section.department h2{ font-weight: 500; font-size: 22px; diff --git a/static/js/ReaderApp.jsx b/static/js/ReaderApp.jsx index 3dd75d07eb..d18da105ec 100644 --- a/static/js/ReaderApp.jsx +++ b/static/js/ReaderApp.jsx @@ -21,7 +21,8 @@ import { EducatorsPage, RabbisPage, DonatePage, - WordByWordPage + WordByWordPage, + JobsPage } from './StaticPages'; import { SignUpModal, @@ -2266,5 +2267,6 @@ export { EducatorsPage, RabbisPage, DonatePage, - WordByWordPage + WordByWordPage, + JobsPage }; diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 4b2bc394d0..a933720a79 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -1,4 +1,4 @@ -import React, {useState, useRef} from 'react'; +import React, {useState, useRef, useEffect, memo} from 'react'; import { SimpleInterfaceBlock, NewsletterSignUpForm, @@ -2636,6 +2636,204 @@ const StaticHR = () => const ConditionalLink = ({ link, children }) => link ? <a href={link} target="_blank">{children}</a> : children; +const JobsPageHeader = ({ jobsAreAvailable }) => { + return ( + <> + <header> + <h1 className="serif"> + <span className="int-en">Jobs at Sefaria</span> + <span className="int-he">משרות פנויות בספריא</span> + </h1> + + {jobsAreAvailable ? ( + <> + <h2> + <span className="int-en">About Sefaria</span> + <span className="int-he">אודות ספריא</span> + </h2> + <p> + <span className="int-en"> + Sefaria is a non-profit organization dedicated to creating the + future of Torah in an open and participatory way. We are assembling + a free, living library of Jewish texts and their interconnections, + in Hebrew and in translation. + </span> + <span className="int-he"> + ספריא היא ארגון ללא מטרות רווח שמטרתו יצירת הדור הבא של לימוד התורה + באופן פתוח ומשותף. אנחנו בספריא מרכיבים ספרייה חיה וחופשית של טקסטים + יהודיים וכלל הקישורים ביניהם, בשפת המקור ובתרגומים. + </span> + </p> + </> + ) : null} + </header> + </> + ); +}; + +const Job = ({ job }) => { + return ( + <div className="job"> + <a className="joblink" target="_blank" href={job.jobLink}> + {job.jobDescription} + </a> + </div> + ); +}; + +const JobsListForDepartment = ({ jobsList }) => { + return ( + <section className="jobsListForDepartment"> + {jobsList.map((job) => ( + <Job key={job.id} job={job} /> + ))} + </section> + ); +}; + +const DepartmentJobPostings = ({ department, departmentJobPostings }) => { + return ( + <section className="section department englishOnly"> + <header> + <h2 className="anchorable">{department}</h2> + </header> + <JobsListForDepartment key={department} jobsList={departmentJobPostings} /> + </section> + ); +}; + +const JobPostingsByDepartment = ({ jobPostings }) => { + const groupedJobPostings = jobPostings.reduce((jobPostingsGroupedByDepartment, jobPosting) => { + const category = jobPosting.jobDepartmentCategory; + if (!jobPostingsGroupedByDepartment[category]) { + jobPostingsGroupedByDepartment[category] = []; + } + jobPostingsGroupedByDepartment[category].push(jobPosting); + return jobPostingsGroupedByDepartment; + }, {}); + + return ( + Object.entries(groupedJobPostings).map(([department, departmentJobPostings]) => { + return ( + <DepartmentJobPostings + key={department} + department={department} + departmentJobPostings={departmentJobPostings} + /> + ); + }) + ); +}; + + +const NoJobsNotice = () => { + return ( + <div className="section nothing"> + <p> + <span className="int-en"> + Sefaria does not currently have any open positions. + Please follow us on <a target="_blank" href="http://www.facebook.com/sefaria.org" >Facebook</a> + to hear about our next openings. + </span> + <span className="int-he"> + ספריא איננה מחפשת כעת עובדים חדשים. + עקבו אחרינו ב<a target="_blank" href="http://www.facebook.com/sefaria.org" >פייסבוק</a>  + כדי להשאר מעודכנים במשרות עתידיות. + </span> + </p> + </div> + ); +}; + +const JobsPage = memo(() => { + const [jobPostings, setJobPostings] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchJobsJSON = async () => { + const query = ` + query { + jobPostings(pagination: { limit: -1 }) { + data { + id + attributes { + jobLink + jobDescription + jobDepartmentCategory + } + } + } + } + `; + try { + const response = await fetch(STRAPI_INSTANCE + "/graphql", { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "omit", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify({ query }), + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + throw error; + } + }; + + const loadJobPostings = async () => { + if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { + try { + const jobsData = await fetchJobsJSON(); + + const jobsFromStrapi = jobsData.data?.jobPostings?.data?.map((jobPosting) => { + return { + id: jobPosting.id, + jobLink: jobPosting.attributes.jobLink, + jobDescription: jobPosting.attributes.jobDescription, + jobDepartmentCategory: jobPosting.attributes.jobDepartmentCategory + .split("_") + .join(" "), + }; + }); + + setJobPostings(jobsFromStrapi); + } catch (error) { + console.error("Fetch error:", error); + setError("Error: Sefaria's CMS cannot be reached"); + } + } else { + setError("Error: Sefaria's CMS cannot be reached"); + } + }; + + loadJobPostings(); + }, []); + + return ( + <div> + {error ? ( + <h1>{error}</h1> + ) : ( + <> + <JobsPageHeader jobsAreAvailable={jobPostings?.length} /> + {jobPostings?.length ? ( + <JobPostingsByDepartment jobPostings={jobPostings} /> + ) : ( + <NoJobsNotice /> + )} + </> + )} + </div> + ); +}); export { RemoteLearningPage, @@ -2648,5 +2846,6 @@ export { EducatorsPage, RabbisPage, DonatePage, - WordByWordPage + WordByWordPage, + JobsPage } diff --git a/templates/static/jobs.html b/templates/static/jobs.html index 859ee3fa9e..fafc387197 100644 --- a/templates/static/jobs.html +++ b/templates/static/jobs.html @@ -5,117 +5,22 @@ {% block description %}{% trans "Job openings at Sefaria, a non-profit technology organization dedicated to building the Jewish future." %}{% endblock %} +{% block js %} +<script> + {% autoescape off %} + DJANGO_VARS.containerId = 'jobsPageContent'; + DJANGO_VARS.reactComponentName = 'JobsPage'; + {% endautoescape %} +</script> +{% endblock %} + {% block content %} <main id="jobsPage" class="container biReady static"> <div class="inner"> - <header> - <h1 class="serif"> - <span class="int-en">Jobs at Sefaria</span> - <span class="int-he">משרות פנויות בספריא</span> - </h1> - - <!-- Comment out when jobs page has no content --> - <h2> - <span class="int-en">About Sefaria</span> - <span class="int-he">אודות ספריא</span> - </h2> - <p> - <span class="int-en"> - Sefaria is a non-profit organization dedicated to creating the future of Torah in an open and - participatory way. - We are assembling a free, living library of Jewish texts and their interconnections, in Hebrew and - in translation. - </span> - <span class="int-he"> - ספריא היא ארגון ללא מטרות רווח שמטרתו יצירת הדור הבא של לימוד התורה באופן פתוח ומשותף. - אנחנו בספריא מרכיבים ספרייה חיה וחופשית של טקסטים יהודיים וכלל הקישורים ביניהם, בשפת המקור ובתרגומים. - </span> - </p> - <!-- Comment out when jobs page has no content --> - </header> - - - <!---<section class="section department englishOnly"> - <header> - <h2 class="anchorable">Operations</h2> - </header> - <section class="jobsListForDepartment"> - <div class="job"><a class="jobLink" target="_blank" href=""></a></div> - </section> - </section>---> - <!-- - <section class="section department englishOnly"> - <header> - <h2 class="anchorable">Engineering</h2> - </header> - <section class="jobsListForDepartment"> - </section> - </section> - <section class="section department englishOnly"> - <header> - <h2 class="anchorable">Learning</h2> - </header> - <section class="jobsListForDepartment"> - <div class="job"><a class="jobLink" target="_blank" href=""></a></div> - <div class="job"><a class="jobLink" target="_blank" href=""></a></div> - </section> - </section> - <section class="section department englishOnly"> - <header> - <h2 class="anchorable">HR and Operations</h2> - </header> - <section class="jobsListForDepartment"> - <div class="job"><a class="" target="_blank" href=""></a></div> - </section> - </section> --> - - <section class="section department englishOnly"> - <header> - <h2 class="anchorable">Marketing and Communications</h2> - </header> - <section class="jobsListForDepartment"> - <div class="job"><a class="joblink" target="_blank" href="https://sefaria.breezy.hr/p/b11c89877ad6-communications-specialist?state=published">Communications Specialist</a></div> - </section> - </section> - - - - - <!-- - <section class="section department englishOnly"> - <header> - <h2 class="anchorable">Product</h2> - </header> - <section class="jobsListForDepartment"> - <div class="job"><a class="jobLink" target="_blank" href=""></a></div> - </section> - <section class="jobsListForDepartment"> - <div class="job"><a class="jobLink" target="_blank" href=""></a></div> - </section> - </section> - <section class="section department englishOnly"> - <header> - <h2 class="anchorable">Israel Team</h2> - </header> - </section>---> - - <!-- - <div class="section nothing"> - <p> - <span class="int-en"> - Sefaria does not currently have any open positions. - Please follow us on <a _target="blank" href="http://www.facebook.com/sefaria.org" >Facebook</a> - to hear about our next openings. - </span> - <span class="int-he"> - ספריא איננה מחפשת כעת עובדים חדשים. - עקבו אחרינו ב<a _target="blank" href="http://www.facebook.com/sefaria.org" >פייסבוק</a> - כדי להשאר מעודכנים במשרות עתידיות. - </span> - </p> + <div id="jobsPageContent"> + <h1 id="appLoading">Loading...</h1> </div> - --> <aside class="solicitationNotice"> <p> From 08ceea5d542ff8103aa6df813e8e2a995b0cf9c2 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Wed, 1 Nov 2023 18:18:34 -0400 Subject: [PATCH 098/260] Fix small bug: change class to className --- static/js/StaticPages.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 7d0af1e4d2..f765b167c9 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2878,8 +2878,8 @@ const TeamMembersPage = memo(() => { </section> <header> <h2> - <span class="int-en">BOARD OF DIRECTORS</span> - <span class="int-he">מועצת המנהלים</span> + <span className="int-en">BOARD OF DIRECTORS</span> + <span className="int-he">מועצת המנהלים</span> </h2> </header> <section className="main-text board-members"> From 3e92cfcb8e50bb64a7167e8b4e77c00bb4e98014 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Wed, 1 Nov 2023 19:16:47 -0400 Subject: [PATCH 099/260] feat(jobs): Sort team members in the correct order when the interface language is Hebrew --- static/js/StaticPages.jsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index f765b167c9..2b603d67e9 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2640,10 +2640,13 @@ const ConditionalLink = ({ link, children }) => * Team Page */ -const byLastName = (a, b) => { - const lastNameA = a.teamMemberDetails.teamName.en.split(" ").pop(); - const lastNameB = b.teamMemberDetails.teamName.en.split(" ").pop(); - return lastNameA.localeCompare(lastNameB); +const byLastName = () => { + const locale = Sefaria.interfaceLang === "hebrew" ? "he" : "en"; + return (a, b) => { + const lastNameA = a.teamMemberDetails.teamName[locale].split(" ").pop(); + const lastNameB = b.teamMemberDetails.teamName[locale].split(" ").pop(); + return lastNameA.localeCompare(lastNameB, locale); + }; }; const partition = (arr, prop) => @@ -2741,7 +2744,7 @@ const BoardMembers = ({ boardMembers }) => { {cofounderBoardMembers.map((boardMember) => ( <BoardMember key={boardMember.id} boardMember={boardMember} /> ))} - {regularBoardMembers.sort(byLastName).map((boardMember) => ( + {regularBoardMembers.sort(byLastName()).map((boardMember) => ( <BoardMember key={boardMember.id} boardMember={boardMember} /> ))} </> @@ -2869,7 +2872,7 @@ const TeamMembersPage = memo(() => { <> <section className="main-text team-members"> <TeamMembers - teamMembers={ordinaryTeamMembers.sort(byLastName)} + teamMembers={ordinaryTeamMembers.sort(byLastName())} /> <Placeholders teamMembersCount={ordinaryTeamMembers.length} From e5f8a3d1ad69d88afe47045cd0c787fe06abc8c4 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 5 Nov 2023 08:43:43 +0200 Subject: [PATCH 100/260] chore(tests): remain tests db after done. --- build/ci/sandbox-values.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/ci/sandbox-values.yaml b/build/ci/sandbox-values.yaml index 776d232677..5cd20dc863 100644 --- a/build/ci/sandbox-values.yaml +++ b/build/ci/sandbox-values.yaml @@ -1,6 +1,8 @@ sandbox: "true" contentSandbox: "true" -deployEnv: +restore: + cleanup: false +deployEnv: previousServicesCount: "1" web: containerImage: From ff8b266c73064c4d023b3b5d143b49e2cfd08a8c Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 9 Nov 2023 11:05:20 +0200 Subject: [PATCH 101/260] feat(api): get texts from new api. --- static/js/sefaria/textGetter.js | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 static/js/sefaria/textGetter.js diff --git a/static/js/sefaria/textGetter.js b/static/js/sefaria/textGetter.js new file mode 100644 index 0000000000..572fe3cc48 --- /dev/null +++ b/static/js/sefaria/textGetter.js @@ -0,0 +1,34 @@ +import Sefaria from "./sefaria"; + +function makeParamsString(language, versionTitle) { + if (versionTitle) { + return `${language}|${versionTitle}`; + } else if (language) { + return language; + } +} + +function makeUrl(ref, requiredVersions) { + const host = Sefaria.apiHost; + const endPoint = '/api/v3/texts/' + const versions = Object.entries(requiredVersions).map(([language, versionTitle]) => + makeParamsString(language, versionTitle) + ); + const url = `${host}${endPoint}${ref}?version=${versions.join('&version=')}&fill_in_missing_segments=true`; + return url; +} + +async function getVTextsFromAPI(ref, requiredVersions) { + const url = makeUrl(ref, requiredVersions); + const apiObject = await Sefaria._ApiPromise(url); + Sefaria.saveVersions(ref, apiObject.available_versions); + delete apiObject.available_versions; + return apiObject; +} + +export async function getTexts(ref, requiredVersions) { + // ref is segment ref or bottom level section ref + // requiredVersions is array of objects that can have language and versionTitle + let returnObj = await getVersionsFromAPI(ref, requiredVersions); + return returnObj; +} From da7f921bd85673055aebebd48a64ffcf45b4adc7 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Thu, 9 Nov 2023 16:36:15 +0200 Subject: [PATCH 102/260] ci: Temporarily disable sandbox uninstall --- .github/workflows/continuous.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 3fa4611d49..c5056a095f 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -389,5 +389,5 @@ jobs: NAMESPACE: ${{ secrets.DEV_SANDBOX_NAMESPACE }} NAME: sandbox-${{ steps.get-sha.outputs.sha_short }} - name: Uninstall - run: helm delete sandbox-${{ steps.get-sha.outputs.sha_short }} -n ${{ secrets.DEV_SANDBOX_NAMESPACE }} --debug --timeout 10m0s + run: echo "helm delete sandbox-${{ steps.get-sha.outputs.sha_short }} -n ${{ secrets.DEV_SANDBOX_NAMESPACE }} --debug --timeout 10m0s" if: steps.get-helm.outputs.count > 0 From 88abb8f97acabdd6dc79be4639b94c4be0d0c7d2 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 12 Nov 2023 09:31:27 +0200 Subject: [PATCH 103/260] refactor(VersionBlock): Extract new component OpenVersion from VersionBlock, relocating functions (onVersionTitleClick, onSelectVersionClick, makeVersionLink) with updated names. Note: The following changes were necessitated by the above refactor and occurred as a result of moving functions to a new component: 1. Handle Hebrew translations in the same way as non-Hebrew. 2. Fix a bug related to single-section books where clicking on versionTitle would reload the page instead of opening the version in the sidebar. 3. Streamline the functions by removing unnecessary conditions based on VersionBlock.props for consistent behavior. --- static/js/OpenVersion.jsx | 75 +++++++++++++++++++++++++++++++++ static/js/VersionBlock.jsx | 86 +++++++++++--------------------------- 2 files changed, 99 insertions(+), 62 deletions(-) create mode 100644 static/js/OpenVersion.jsx diff --git a/static/js/OpenVersion.jsx b/static/js/OpenVersion.jsx new file mode 100644 index 0000000000..87ae522aed --- /dev/null +++ b/static/js/OpenVersion.jsx @@ -0,0 +1,75 @@ +import React from 'react'; +import Sefaria from "./sefaria/sefaria"; +import PropTypes from "prop-types"; + +function OpenVersion({currRef, version, currObjectVersions, targetPanel, string, className, openVersionInSidebar, openVersionInReader, rendermode, firstSectionRef}) { + + const mainPanel = targetPanel === 'main'; + function makeVersionLink() { + // maintain all versions for languages you're not currently selecting + if (version.merged) { + return "#"; // there's no url for a merged version + } + const withParam = mainPanel ? "" : "&with=Translation Open"; + const versionParam = mainPanel ? version.language : 'side'; + const nonSelectedVersionParams = Object.entries(currObjectVersions) + .filter(([vlang, ver]) => !!ver && !!ver?.versionTitle && !version?.merged && (withParam || vlang !== version.language)) // in 'side' case, keep all version params + .map(([vlang, ver]) => `&v${vlang}=${ver.versionTitle.replace(/\s/g,'_')}`) + .join(""); + const versionLink = nonSelectedVersionParams === "" ? null : `/${Sefaria.normRef(currRef)}${nonSelectedVersionParams}&v${versionParam}=${version.versionTitle.replace(/\s/g,'_')}${withParam}`.replace("&","?"); + return versionLink; + } + + function openInSidebar(e) { + e.preventDefault(); + try { + gtag("event", "onClick_version_title", {element_name: `version_title`, + change_to: `${version.versionTitle}`, change_from: `${currObjectVersions[version.language]['versionTitle']}`, + categories: `${Sefaria.refCategories(currRef)}`, book: `${Sefaria.parseRef(currRef).index}` }) + } + catch(err) { + console.log(err); + } + openVersionInSidebar(version.versionTitle, version.language); + } + + function openInMainPanel(e) { + e.preventDefault(); + try { + gtag("event", "onClick_select_version", {element_name: `select_version`, + change_to: `${version.versionTitle}`, change_from: `${currObjectVersions[version.language]['versionTitle']}`, + categories: `${Sefaria.refCategories(currRef)}`, book: `${Sefaria.parseRef(currRef).index}` }) + } + catch(err) { + console.log(err); + } + if (rendermode === 'book-page') { + window.location = `/${firstSectionRef}?v${version.language}=${version.versionTitle.replace(/\s/g,'_')}`; + } else { + openVersionInReader(version.versionTitle, version.language); + } + Sefaria.setVersionPreference(currRef, version.versionTitle, version.language); + } + + return ( + <a className={className} + href={makeVersionLink()} + onClick={mainPanel ? openInMainPanel : openInSidebar}> + {string} + </a> + ); +} +OpenVersion.prototypes = { + version: PropTypes.object.isRequired, + currObjectVersions: PropTypes.object.isRequired, + currRef: PropTypes.string.isRequired, + className: PropTypes.string, + openVersionInSidebar: PropTypes.func, + openVersionInReader: PropTypes.func.isRequired, + targetPanel: PropTypes.string.isRequired, + string: PropTypes.string, + rendermode: PropTypes.string.isRequired, + firstSectionRef: PropTypes.string, +} + +export default OpenVersion; diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index 40219735a3..4454db3d5f 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -6,6 +6,7 @@ import Util from './sefaria/util'; import $ from './sefaria/sefariaJquery'; import Component from 'react-class'; import {LoadingMessage} from "./Misc"; +import OpenVersion from "./OpenVersion"; @@ -36,40 +37,6 @@ class VersionBlock extends Component { this.updateableVersionAttributes.forEach(attr => s[attr] = props.version[attr]); this.state = s; } - onVersionTitleClick(e) { - e.preventDefault(); - this.index = Sefaria.parseRef(this.props.currentRef).index - try { - gtag("event", "onClick_version_title", {element_name: `version_title`, change_to: `${this.props.version.versionTitle}`, change_from: `${this.props.currObjectVersions[this.props.version.language]['versionTitle']}`, categories: `${Sefaria.refCategories(this.props.currentRef)}`, book: `${Sefaria.parseRef(this.props.currentRef).index}` }) - } - catch(err) { - console.log(err); - } - if (!this.props.openVersionInSidebar && !this.props.openVersionInReader) return; - if (this.props.firstSectionRef) { - window.location = `/${this.props.firstSectionRef}?v${this.props.version.language}=${this.props.version.versionTitle.replace(/\s/g,'_')}`; - } else { - const action = this.props.openVersionInSidebar ? this.props.openVersionInSidebar : this.props.openVersionInReader; - if (action) { - action(this.props.version.versionTitle, this.props.version.language); - } - } - } - onSelectVersionClick(e) { - e.preventDefault(); - try { - gtag("event", "onClick_select_version", {element_name: `select_version`, - change_to: `${this.props.version.versionTitle}`, change_from: `${this.props.currObjectVersions[this.props.version.language]['versionTitle']}`, - categories: `${Sefaria.refCategories(this.props.currentRef)}`, book: `${Sefaria.parseRef(this.props.currentRef).index}` }) - } - catch(err) { - console.log(err); - } - if (this.props.openVersionInReader) { - this.props.openVersionInReader(this.props.version.versionTitle, this.props.version.language); - Sefaria.setVersionPreference(this.props.currentRef, this.props.version.versionTitle, this.props.version.language); - } - } handleInputChange(event) { const target = event.target; const name = target.name; @@ -145,22 +112,6 @@ class VersionBlock extends Component { e.preventDefault(); this.props.viewExtendedNotes(this.props.version.title, this.props.version.language, this.props.version.versionTitle); } - makeVersionLink(versionParam) { - //versionParam - either version language (e.g. 'en') in the case when you're making a link for versions in reader - //otherwise, 'side' for making link for versions in sidebar - - // maintain all versions for languages you're not currently selecting - if (this.props.version.merged) { - return "#"; // there's no url for a merged version - } - const withParam = versionParam === 'side' ? "&with=Translation Open" : ""; - const nonSelectedVersionParams = Object.entries(this.props.currObjectVersions) - .filter(([vlang, version])=>!!version &&!!version?.versionTitle && !version?.merged && (versionParam === 'side' || vlang !== this.props.version.language)) // in 'side' case, keep all version params - .map(([vlang, version])=>`&v${vlang}=${version.versionTitle.replace(/\s/g,'_')}`) - .join(""); - const versionLink = nonSelectedVersionParams == "" ? null : `/${Sefaria.normRef(this.props.currentRef)}${nonSelectedVersionParams}&v${versionParam}=${this.props.version.versionTitle.replace(/\s/g,'_')}${withParam}`.replace("&","?"); - return versionLink; - } makeVersionTitle(){ if(this.props.version.merged){ return {"className": "", "text": Sefaria._("Merged from") + " " + Array.from(new Set(this.props.version.sources)).join(", ")}; @@ -187,7 +138,6 @@ class VersionBlock extends Component { return (this.props.version.license in license_map) ? license_map[this.props.version.license] : "#"; } makeSelectVersionLanguage(){ - if (this.isHeTranslation() && !this.props.isCurrent) { return Sefaria._("View in Sidebar"); } let voc = this.props.version.isBaseText ? 'Version' : "Translation"; return this.props.isCurrent ? Sefaria._("Current " + voc) : Sefaria._("Select "+ voc); } @@ -208,9 +158,6 @@ class VersionBlock extends Component { return !!this.props.version.purchaseInformationImage ? this.props.version.purchaseInformationImage : "data:,"; } - isHeTranslation() { - return this.props.version.actualLanguage === 'he' && !this.props.version.isBaseText && this.props.inTranslationBox; - } render() { if(this.props.version.title == "Sheet") return null //why are we even getting here in such a case??; const v = this.props.version; @@ -286,19 +233,34 @@ class VersionBlock extends Component { <div className="versionBlock"> <div className="versionBlockHeading"> <div className="versionTitle" role="heading"> - <a className={vtitle["className"]} href={this.makeVersionLink('side')} onClick={this.onVersionTitleClick}> - {vtitle["text"]} - </a> + <OpenVersion + version={this.props.version} + currRef={this.props.currentRef} + currObjectVersions={this.props.currObjectVersions} + className={vtitle["className"]} + openVersionInSidebar={this.props.openVersionInSidebar} + openVersionInReader={this.props.openVersionInReader} + targetPanel={this.props.rendermode === 'book-page' ? 'main' : 'side'} + string={vtitle["text"]} + rendermode={this.props.rendermode} + firstSectionRef={this.props.firstSectionRef} + /> </div> <i className={`fa fa-pencil versionEditIcon ${(Sefaria.is_moderator && this.props.rendermode == "book-page") ? "enabled" : ""}`} aria-hidden="true" onClick={this.openEditor}/> <div className="versionLanguage sans-serif">{showLanguagLabel ? Sefaria._(Sefaria.translateISOLanguageCode(v.actualLanguage)) : ""}</div> </div> <div className="versionSelect sans-serif"> - <a className={`selectButton ${this.props.isCurrent ? "currSelectButton": this.isHeTranslation() ? "heTranslation" : ""}`} - href={this.makeVersionLink(v.language)} - onClick={this.isHeTranslation() ? this.onVersionTitleClick : this.onSelectVersionClick}> - {this.makeSelectVersionLanguage()} - </a> + <OpenVersion + version={this.props.version} + currRef={this.props.currentRef} + currObjectVersions={this.props.currObjectVersions} + className={`selectButton ${this.props.isCurrent ? "currSelectButton" : ""}`} + openVersionInSidebar={this.props.openVersionInSidebar} + openVersionInReader={this.props.openVersionInReader} + targetPanel='main' + string={this.makeSelectVersionLanguage()} + rendermode={this.props.rendermode} + /> </div> <div className={classNames(this.makeAttrClassNames({"versionNotes": 1, "sans-serif": (this.props.rendermode == "book-page")}, "versionNotes", true))}> <span className="" dangerouslySetInnerHTML={ {__html: vnotes} } /> From 6027f64d14ccbbb0e697e6eb7019ec0944070307 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 12 Nov 2023 09:48:09 +0200 Subject: [PATCH 104/260] refactor(VersionBlock): change string to text. --- static/js/OpenVersion.jsx | 6 +++--- static/js/VersionBlock.jsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/static/js/OpenVersion.jsx b/static/js/OpenVersion.jsx index 87ae522aed..a716a2d6d8 100644 --- a/static/js/OpenVersion.jsx +++ b/static/js/OpenVersion.jsx @@ -2,7 +2,7 @@ import React from 'react'; import Sefaria from "./sefaria/sefaria"; import PropTypes from "prop-types"; -function OpenVersion({currRef, version, currObjectVersions, targetPanel, string, className, openVersionInSidebar, openVersionInReader, rendermode, firstSectionRef}) { +function OpenVersion({currRef, version, currObjectVersions, targetPanel, text, className, openVersionInSidebar, openVersionInReader, rendermode, firstSectionRef}) { const mainPanel = targetPanel === 'main'; function makeVersionLink() { @@ -55,7 +55,7 @@ function OpenVersion({currRef, version, currObjectVersions, targetPanel, string, <a className={className} href={makeVersionLink()} onClick={mainPanel ? openInMainPanel : openInSidebar}> - {string} + {text} </a> ); } @@ -67,7 +67,7 @@ OpenVersion.prototypes = { openVersionInSidebar: PropTypes.func, openVersionInReader: PropTypes.func.isRequired, targetPanel: PropTypes.string.isRequired, - string: PropTypes.string, + text: PropTypes.string, rendermode: PropTypes.string.isRequired, firstSectionRef: PropTypes.string, } diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index 4454db3d5f..573b555282 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -241,7 +241,7 @@ class VersionBlock extends Component { openVersionInSidebar={this.props.openVersionInSidebar} openVersionInReader={this.props.openVersionInReader} targetPanel={this.props.rendermode === 'book-page' ? 'main' : 'side'} - string={vtitle["text"]} + text={vtitle["text"]} rendermode={this.props.rendermode} firstSectionRef={this.props.firstSectionRef} /> From dd79d4a4c5931a1ae880cf614a6edc5a130cf40b Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Sun, 12 Nov 2023 14:53:57 +0200 Subject: [PATCH 105/260] chore: added 'picture' to AdminEditor --- static/js/AdminEditor.jsx | 19 ++++++++++++++++++- static/js/Misc.jsx | 15 +++++++++------ static/js/TopicEditor.jsx | 7 ++++--- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/static/js/AdminEditor.jsx b/static/js/AdminEditor.jsx index 7061b4b7bf..68d0c62903 100644 --- a/static/js/AdminEditor.jsx +++ b/static/js/AdminEditor.jsx @@ -1,9 +1,10 @@ import React, {useRef, useState} from "react"; import Sefaria from "./sefaria/sefaria"; -import {AdminToolHeader, InterfaceText, TitleVariants} from "./Misc"; +import {AdminToolHeader, InterfaceText, ProfilePic, TitleVariants} from "./Misc"; import sanitizeHtml from 'sanitize-html'; import classNames from "classnames"; const options_for_form = { + "Picture": {label: "Picture", field: "picture", placeholder: "Add a picture.", type: "picture"}, "Title": {label: "Title", field: "enTitle", placeholder: "Add a title."}, "Hebrew Title": {label: "Hebrew Title", field: "heTitle", placeholder: "Add a title."}, "English Description": { @@ -125,6 +126,10 @@ const AdminEditor = ({title, data, close, catMenu, updateData, savingStatus, } updateData({...data}); } + const handlePictureChange = (url) => { + data["picture"] = url; + updateData({...data}); + } const handleTitleVariants = (newTitles, field) => { const newData = {...data}; newData[field] = newTitles.map(x => Object.assign({}, x)); @@ -158,9 +163,21 @@ const AdminEditor = ({title, data, close, catMenu, updateData, savingStatus, </select> </div>; } + const getPicture = (label, field) => { + return <ProfilePic + saveCallback={(url) => handlePictureChange(url)} + showButtons={true} + name={""} + url={data[field]} + len={60} + />; + } const item = ({label, field, placeholder, type, dropdown_data}) => { let obj; switch(type) { + case 'picture': + obj = getPicture(label, field); + break; case 'dropdown': obj = getDropdown(field, dropdown_data, placeholder); break; diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 0b1a96cc3d..7d3aacae68 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -262,10 +262,11 @@ class ProfilePic extends Component { if (response.error) { throw new Error(response.error); } else { - this.closePopup({ cb: () => { + const defaultCallback = () => { window.location = "/profile/" + Sefaria.slug; // reload to get update return; - }}); + }; + this.closePopup({ cb: this.props.saveCallback ? this.props.saveCallback(response.urls[0]) : defaultCallback}); } } catch (e) { errored = true; @@ -352,11 +353,12 @@ class ProfilePic extends Component { } } ProfilePic.propTypes = { - url: PropTypes.string, - name: PropTypes.string, - len: PropTypes.number, + url: PropTypes.string, + saveCallback: PropTypes.func, // used by AdminEditor to override default callback upon save + name: PropTypes.string, + len: PropTypes.number, hideOnDefault: PropTypes.bool, // hide profile pic if you have are displaying default pic - showButtons: PropTypes.bool, // show profile pic action buttons + showButtons: PropTypes.bool, // show profile pic action buttons }; @@ -1234,6 +1236,7 @@ const EditorForExistingTopic = ({ toggle, data }) => { origDeathPlace: data?.properties?.deathPlace?.value, origDeathYear: data?.properties?.deathYear?.value, origEra: data?.properties?.era?.value + }; const origWasCat = "displays-above" in data?.links; diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 3d7dd86f74..229ba62da2 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -1,5 +1,5 @@ import Sefaria from "./sefaria/sefaria"; -import {InterfaceText, requestWithCallBack, TitleVariants, ToggleSet} from "./Misc"; +import {InterfaceText, requestWithCallBack, ProfilePic} from "./Misc"; import $ from "./sefaria/sefariaJquery"; import {AdminEditor} from "./AdminEditor"; import {Reorder} from "./CategoryEditor"; @@ -18,7 +18,8 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { birthPlace: origData.origBirthPlace || "", heBirthPlace: origData.origHeBirthPlace || "", birthYear: origData.origBirthYear || "", heDeathPlace: origData.origHeDeathPlace || "", deathYear: origData.origDeathYear || "", era: origData.origEra || "", - deathPlace: origData.origDeathPlace || "" + deathPlace: origData.origDeathPlace || "", + picture: origData?.origPicture || "" }); const isNew = !('origSlug' in origData); const [savingStatus, setSavingStatus] = useState(false); @@ -159,7 +160,7 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { const url = `/api/topic/delete/${data.origSlug}`; requestWithCallBack({url, type: "DELETE", redirect: () => window.location.href = "/topics"}); } - let items = ["Title", "Hebrew Title", "English Description", "Hebrew Description", "Category Menu"]; + let items = ["Title", "Hebrew Title", "English Description", "Hebrew Description", "Category Menu", "Picture"]; if (isCategory) { items.push("English Short Description"); items.push("Hebrew Short Description"); From 622e30e9914b363dd48627298ed6947450f91914 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 12 Nov 2023 19:11:53 +0200 Subject: [PATCH 106/260] fix(VersionBlock): remove isRequired from openVersionInReader. --- static/js/OpenVersion.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/OpenVersion.jsx b/static/js/OpenVersion.jsx index a716a2d6d8..d31c061493 100644 --- a/static/js/OpenVersion.jsx +++ b/static/js/OpenVersion.jsx @@ -65,7 +65,7 @@ OpenVersion.prototypes = { currRef: PropTypes.string.isRequired, className: PropTypes.string, openVersionInSidebar: PropTypes.func, - openVersionInReader: PropTypes.func.isRequired, + openVersionInReader: PropTypes.func, targetPanel: PropTypes.string.isRequired, text: PropTypes.string, rendermode: PropTypes.string.isRequired, From bf28e49574d160128c8cdc8c0a057d95b45eff79 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 15 Nov 2023 09:15:17 +0200 Subject: [PATCH 107/260] test(api): tests for one base, source and translation. --- api/tests.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/tests.py b/api/tests.py index df63d460ad..684e782146 100644 --- a/api/tests.py +++ b/api/tests.py @@ -33,6 +33,13 @@ def test_api_get_text_source_all(self): self.assertEqual(data["sections"], ["22a"]) self.assertEqual(data["toSections"], ["22a"]) + def test_api_get_text_source(self): + response = c.get('/api/v3/texts/Shabbat.22a?version=source') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data["versions"]), 1) + self.assertEqual(data["versions"][0]['versionTitle'], "William Davidson Edition - Vocalized Aramaic") + def test_api_get_text_translation_all(self): response = c.get('/api/v3/texts/Shabbat.22a?version=translation|all') self.assertEqual(200, response.status_code) @@ -44,6 +51,13 @@ def test_api_get_text_translation_all(self): self.assertEqual(data["sections"], ["22a"]) self.assertEqual(data["toSections"], ["22a"]) + def test_api_get_text_translation(self): + response = c.get('/api/v3/texts/Shabbat.22a?version=translation') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data["versions"]), 1) + self.assertEqual(data["versions"][0]['versionTitle'], "William Davidson Edition - English") + def test_api_get_text_lang_all(self): response = c.get('/api/v3/texts/Rashi_on_Genesis.2.3?version=en|all') self.assertEqual(200, response.status_code) @@ -75,6 +89,13 @@ def test_api_get_text_base_all(self): self.assertTrue(len(data["versions"]) > 3) self.assertTrue(all(v['actualLanguage'] == 'he' for v in data["versions"])) + def test_api_get_text_base(self): + response = c.get('/api/v3/texts/Shabbat.22a?version=base') + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertEqual(len(data["versions"]), 1) + self.assertEqual(data["versions"][0]['versionTitle'], "William Davidson Edition - Vocalized Aramaic") + def test_api_get_text_two_params(self): response = c.get('/api/v3/texts/Genesis.1?version=he|Tanach with Nikkud&version=en|all') data = json.loads(response.content) From 7b4bd2e7fba9d0de43f024b64506e4dd4cf4db77 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 15 Nov 2023 09:18:03 +0200 Subject: [PATCH 108/260] doc(api): correct doc string for direction attribute of Version. --- sefaria/model/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 02f4ce26d3..d3e589f0b0 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1338,7 +1338,7 @@ class Version(AbstractTextRecord, abst.AbstractMongoRecord, AbstractSchemaConten "isBaseText", 'isSource', 'isPrimary', - 'direction', # 1 for rtl, 2 for ltr + 'direction', # 'rtl' or 'ltr' ] def __str__(self): From 1801a0dc82c1f7f05042f69dc5835288f85d5047 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 15 Nov 2023 09:22:35 +0200 Subject: [PATCH 109/260] feat(api): add direction attr to version when saved. --- sefaria/model/text.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index d3e589f0b0..f065d30756 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1400,6 +1400,9 @@ def _normalize(self): else: self.actualLanguage = self.language + if not getattr(self, 'direction', None): + self.direction = 'rtl' if self.language == 'he' else 'ltr' + if getattr(self, "priority", None): try: self.priority = float(self.priority) From f7936f89b55ad013ad810c6345ed7c40809f5915 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 16 Nov 2023 16:38:00 +0200 Subject: [PATCH 110/260] feat(api): add return_format param that can be default, text_only (for stripping notes and html), or wrap_all_entities (for adding html for ref links and topic links). --- api/views.py | 3 +- sefaria/model/text_manager.py | 61 ++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/api/views.py b/api/views.py index 241c95e290..ec2f67077f 100644 --- a/api/views.py +++ b/api/views.py @@ -47,7 +47,8 @@ def get(self, request, *args, **kwargs): versions_params = ['base'] versions_params = [self.split_piped_params(param_str) for param_str in versions_params] fill_in_missing_segments = request.GET.get('fill_in_missing_segments', False) - text_manager = TextManager(self.oref, versions_params, fill_in_missing_segments) + return_format = request.GET.get('return_format', 'default') + text_manager = TextManager(self.oref, versions_params, fill_in_missing_segments, return_format) data = text_manager.get_versions_for_query() data = self._handle_warnings(data) return jsonResponse(data) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 85640548aa..8a1b4fd004 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -1,11 +1,13 @@ import copy - +from collections import defaultdict +from functools import partial, reduce +from typing import List import django django.setup() from sefaria.model import * from sefaria.utils.hebrew import hebrew_term -from typing import List - +from sefaria.system.exceptions import InputError +from sefaria.datatype.jagged_array import JaggedTextArray class TextManager: ALL = 'all' @@ -13,10 +15,11 @@ class TextManager: SOURCE = 'source' TRANSLATION = 'translation' - def __init__(self, oref: Ref, versions_params: List[List[str]], fill_in_missing_segments=True): + def __init__(self, oref: Ref, versions_params: List[List[str]], fill_in_missing_segments=True, return_format='default'): self.versions_params = versions_params self.oref = oref self.fill_in_missing_segments = fill_in_missing_segments + self.return_format = return_format self.handled_version_params = [] self.all_versions = self.oref.versionset() @@ -133,10 +136,60 @@ def _add_node_data_to_return_obj(self) -> None: if not inode.is_virtual: self.return_obj['index_offsets_by_depth'] = inode.trim_index_offsets_by_sections(self.oref.sections, self.oref.toSections) + def _format_text(self): + def find_language(version): + return 'he' if version['direction'] == 'rtl' else 'en' # this is because we remove the language attr. do we want to do it later? + + def wrap_links(string, language): + link_wrapping_reg, title_nodes = library.get_regex_and_titles_for_ref_wrapping( + string, lang=language, citing_only=True) + return library.get_wrapped_refs_string(string, lang=language, citing_only=True, + reg=link_wrapping_reg, title_nodes=title_nodes) + + if self.return_format == 'wrap_all_entities': + all_segment_refs = self.oref.all_segment_refs() + query = self.oref.ref_regex_query() + query.update({"inline_citation": True}) + if Link().load(query): + text_modification_funcs = [lambda string, _: wrap_links(string, language)] + + elif self.return_format == 'text_only': + text_modification_funcs = [lambda string, _: text.AbstractTextRecord.strip_itags(string), + lambda string, _: text.AbstractTextRecord.remove_html(string), + lambda string, _: ' '.join(string.split())] + + else: + return + + def make_named_entities_dict(version, language): + named_entities = RefTopicLinkSet({"expandedRefs": {"$in": [r.normal() for r in all_segment_refs]}, + "charLevelData.versionTitle": version['versionTitle'], + "charLevelData.language": language}) + # assumption is that refTopicLinks are all to unranged refs + ne_by_secs = defaultdict(list) + for ne in named_entities: + try: + ne_ref = Ref(ne.ref) + except InputError: + continue + ne_by_secs[ne_ref.sections[-1]-1,] += [ne] + return ne_by_secs + + for version in self.return_obj['versions']: + if self.return_format == 'wrap_all_entities': + language = find_language(version) + ne_by_secs = make_named_entities_dict(version, language) + text_modification_funcs.append(lambda string, sections: library.get_wrapped_named_entities_string(ne_by_secs[(sections[-1],)], string)) + + ja = JaggedTextArray(version['text']) # JaggedTextArray works also with depth 0, i.e. a string + composite_func = lambda string, sections: reduce(lambda s, f: f(s, sections), text_modification_funcs, string) # wrap all functions into one function + version['text'] = ja.modify_by_function(composite_func) + def get_versions_for_query(self) -> dict: for lang, vtitle in self.versions_params: self._append_required_versions(lang, vtitle) self._add_ref_data_to_return_obj() self._add_index_data_to_return_obj() self._add_node_data_to_return_obj() + self._format_text() return self.return_obj From 12bb05c5596c8b5a9dc6e310d6acfe546ac541f6 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 16 Nov 2023 16:38:17 +0200 Subject: [PATCH 111/260] test(api): test return_format. --- api/tests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/api/tests.py b/api/tests.py index 684e782146..78549980f8 100644 --- a/api/tests.py +++ b/api/tests.py @@ -196,3 +196,18 @@ def test_without_fill_in_missing_segments(self): data = json.loads(response.content) self.assertEqual(len(data['versions'][0]['text']), 2) self.assertFalse(data['versions'][0].get('sources')) + + def test_wrap_all_entities(self): + vtitle = "The Contemporary Torah, Jewish Publication Society, 2006" + response = c.get(f"/api/v3/texts/Genesis%2010?version=en|{vtitle}&return_format=wrap_all_entities") + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertTrue('<a class ="refLink"' in data['versions'][0]['text'][3]) + self.assertTrue('<a href="/topics' in data['versions'][0]['text'][8]) + + def text_text_only(self): + response = c.get(f"/api/v3/texts/Shulchan_Arukh%2C_Orach_Chayim.1:1?return_format=text_only") + self.assertEqual(200, response.status_code) + data = json.loads(response.content) + self.assertFalse('<' in data['versions'][0]['text']) + From 1232196a9bca3221e513fc36174351a7463477c8 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 16 Nov 2023 17:02:36 +0200 Subject: [PATCH 112/260] refactor(api): remove unnecessary nonlocal params. --- sefaria/model/text_manager.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 8a1b4fd004..b1726d2ab8 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -137,10 +137,10 @@ def _add_node_data_to_return_obj(self) -> None: self.return_obj['index_offsets_by_depth'] = inode.trim_index_offsets_by_sections(self.oref.sections, self.oref.toSections) def _format_text(self): - def find_language(version): + def find_language(): return 'he' if version['direction'] == 'rtl' else 'en' # this is because we remove the language attr. do we want to do it later? - def wrap_links(string, language): + def wrap_links(string): link_wrapping_reg, title_nodes = library.get_regex_and_titles_for_ref_wrapping( string, lang=language, citing_only=True) return library.get_wrapped_refs_string(string, lang=language, citing_only=True, @@ -151,7 +151,7 @@ def wrap_links(string, language): query = self.oref.ref_regex_query() query.update({"inline_citation": True}) if Link().load(query): - text_modification_funcs = [lambda string, _: wrap_links(string, language)] + text_modification_funcs = [lambda string, _: wrap_links(string)] elif self.return_format == 'text_only': text_modification_funcs = [lambda string, _: text.AbstractTextRecord.strip_itags(string), @@ -161,7 +161,7 @@ def wrap_links(string, language): else: return - def make_named_entities_dict(version, language): + def make_named_entities_dict(): named_entities = RefTopicLinkSet({"expandedRefs": {"$in": [r.normal() for r in all_segment_refs]}, "charLevelData.versionTitle": version['versionTitle'], "charLevelData.language": language}) @@ -177,8 +177,8 @@ def make_named_entities_dict(version, language): for version in self.return_obj['versions']: if self.return_format == 'wrap_all_entities': - language = find_language(version) - ne_by_secs = make_named_entities_dict(version, language) + language = find_language() + ne_by_secs = make_named_entities_dict() text_modification_funcs.append(lambda string, sections: library.get_wrapped_named_entities_string(ne_by_secs[(sections[-1],)], string)) ja = JaggedTextArray(version['text']) # JaggedTextArray works also with depth 0, i.e. a string From 1d51f60ee70ce9447fb818fedc65d6874ad2cb53 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 16 Nov 2023 17:04:53 +0200 Subject: [PATCH 113/260] refactor(api): better order of functions. --- sefaria/model/text_manager.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index b1726d2ab8..624018f89f 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -146,6 +146,20 @@ def wrap_links(string): return library.get_wrapped_refs_string(string, lang=language, citing_only=True, reg=link_wrapping_reg, title_nodes=title_nodes) + def make_named_entities_dict(): + named_entities = RefTopicLinkSet({"expandedRefs": {"$in": [r.normal() for r in all_segment_refs]}, + "charLevelData.versionTitle": version['versionTitle'], + "charLevelData.language": language}) + # assumption is that refTopicLinks are all to unranged refs + ne_by_secs = defaultdict(list) + for ne in named_entities: + try: + ne_ref = Ref(ne.ref) + except InputError: + continue + ne_by_secs[ne_ref.sections[-1]-1,] += [ne] + return ne_by_secs + if self.return_format == 'wrap_all_entities': all_segment_refs = self.oref.all_segment_refs() query = self.oref.ref_regex_query() @@ -161,20 +175,6 @@ def wrap_links(string): else: return - def make_named_entities_dict(): - named_entities = RefTopicLinkSet({"expandedRefs": {"$in": [r.normal() for r in all_segment_refs]}, - "charLevelData.versionTitle": version['versionTitle'], - "charLevelData.language": language}) - # assumption is that refTopicLinks are all to unranged refs - ne_by_secs = defaultdict(list) - for ne in named_entities: - try: - ne_ref = Ref(ne.ref) - except InputError: - continue - ne_by_secs[ne_ref.sections[-1]-1,] += [ne] - return ne_by_secs - for version in self.return_obj['versions']: if self.return_format == 'wrap_all_entities': language = find_language() From f4b57c8e643bd6342a2a9761b4f9c1324cc78684 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 16 Nov 2023 17:08:52 +0200 Subject: [PATCH 114/260] fix(api): avoid accumulation of functions in iteration. --- sefaria/model/text_manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 624018f89f..a675c71e11 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -164,8 +164,9 @@ def make_named_entities_dict(): all_segment_refs = self.oref.all_segment_refs() query = self.oref.ref_regex_query() query.update({"inline_citation": True}) + temp_modification_funcs = [] if Link().load(query): - text_modification_funcs = [lambda string, _: wrap_links(string)] + temp_modification_funcs.append(lambda string, _: wrap_links(string)) elif self.return_format == 'text_only': text_modification_funcs = [lambda string, _: text.AbstractTextRecord.strip_itags(string), @@ -179,7 +180,7 @@ def make_named_entities_dict(): if self.return_format == 'wrap_all_entities': language = find_language() ne_by_secs = make_named_entities_dict() - text_modification_funcs.append(lambda string, sections: library.get_wrapped_named_entities_string(ne_by_secs[(sections[-1],)], string)) + text_modification_funcs = temp_modification_funcs + [lambda string, sections: library.get_wrapped_named_entities_string(ne_by_secs[(sections[-1],)], string)] ja = JaggedTextArray(version['text']) # JaggedTextArray works also with depth 0, i.e. a string composite_func = lambda string, sections: reduce(lambda s, f: f(s, sections), text_modification_funcs, string) # wrap all functions into one function From 492f16ffcaca45749458c5febdddb256d9446979 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 16 Nov 2023 17:13:18 +0200 Subject: [PATCH 115/260] refactor(api): move creation of function before its unlocal params. --- sefaria/model/text_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index a675c71e11..8c7f6f9a55 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -164,9 +164,9 @@ def make_named_entities_dict(): all_segment_refs = self.oref.all_segment_refs() query = self.oref.ref_regex_query() query.update({"inline_citation": True}) - temp_modification_funcs = [] + text_modification_funcs = [lambda string, sections: library.get_wrapped_named_entities_string(ne_by_secs[(sections[-1],)], string)] if Link().load(query): - temp_modification_funcs.append(lambda string, _: wrap_links(string)) + text_modification_funcs.append(lambda string, _: wrap_links(string)) elif self.return_format == 'text_only': text_modification_funcs = [lambda string, _: text.AbstractTextRecord.strip_itags(string), @@ -180,7 +180,6 @@ def make_named_entities_dict(): if self.return_format == 'wrap_all_entities': language = find_language() ne_by_secs = make_named_entities_dict() - text_modification_funcs = temp_modification_funcs + [lambda string, sections: library.get_wrapped_named_entities_string(ne_by_secs[(sections[-1],)], string)] ja = JaggedTextArray(version['text']) # JaggedTextArray works also with depth 0, i.e. a string composite_func = lambda string, sections: reduce(lambda s, f: f(s, sections), text_modification_funcs, string) # wrap all functions into one function From b061e8b2e6b10eea0e9eaaa2e74bcdbcec192709 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 16 Nov 2023 18:00:11 +0200 Subject: [PATCH 116/260] refactor(api): remove redundant import. --- sefaria/model/text_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 8c7f6f9a55..3b84084398 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -1,6 +1,6 @@ import copy from collections import defaultdict -from functools import partial, reduce +from functools import reduce from typing import List import django django.setup() From 3ea41f49bc8e3e1962d2f67cc2f25632932ebf56 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 16 Nov 2023 23:11:05 +0200 Subject: [PATCH 117/260] refactor(api): calculate language without function. --- sefaria/model/text_manager.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 3b84084398..c4fb8c4f23 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -137,9 +137,6 @@ def _add_node_data_to_return_obj(self) -> None: self.return_obj['index_offsets_by_depth'] = inode.trim_index_offsets_by_sections(self.oref.sections, self.oref.toSections) def _format_text(self): - def find_language(): - return 'he' if version['direction'] == 'rtl' else 'en' # this is because we remove the language attr. do we want to do it later? - def wrap_links(string): link_wrapping_reg, title_nodes = library.get_regex_and_titles_for_ref_wrapping( string, lang=language, citing_only=True) @@ -178,7 +175,7 @@ def make_named_entities_dict(): for version in self.return_obj['versions']: if self.return_format == 'wrap_all_entities': - language = find_language() + language = 'he' if version['direction'] == 'rtl' else 'en' ne_by_secs = make_named_entities_dict() ja = JaggedTextArray(version['text']) # JaggedTextArray works also with depth 0, i.e. a string From 6efe4f18895c642d1cd9f220fcbddf11b06856b9 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Sun, 19 Nov 2023 15:32:03 -0500 Subject: [PATCH 118/260] Replace placeholders code with CSS to eliminate the need for calculating empty spots and to display correctly in all scenarios --- static/css/static.css | 3 ++- static/js/StaticPages.jsx | 20 -------------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/static/css/static.css b/static/css/static.css index eec62cdfa3..26b59607ec 100644 --- a/static/css/static.css +++ b/static/css/static.css @@ -845,8 +845,9 @@ p.registration-links a:hover{ display: inline-block; margin-bottom: 20px; } -#teamPage .teamMember.placeholder, #teamPage .teamBoardMember.placeholder { +#teamPage .team-members::after, #teamPage .board-members::after { width: 200px; + content: ""; } #teamPage .teamMember { flex: 0 0 30%; diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 2b603d67e9..b1fc96b5a2 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2701,18 +2701,6 @@ const TeamMembers = ({ teamMembers }) => ( </> ); -const Placeholders = ({ teamMembersCount, cls }) => { - const placeholdersCount = - 3 - (teamMembersCount - 3 * Math.floor(teamMembersCount / 3)); - return ( - <> - {Array.from({ length: placeholdersCount }, (_, index) => ( - <div key={index} className={`${cls} placeholder`} /> - ))} - </> - ); -}; - const BoardMember = ({ boardMember }) => ( <div className="teamBoardMember"> <TeamMemberDetails teamMemberDetails={boardMember.teamMemberDetails} /> @@ -2874,10 +2862,6 @@ const TeamMembersPage = memo(() => { <TeamMembers teamMembers={ordinaryTeamMembers.sort(byLastName())} /> - <Placeholders - teamMembersCount={ordinaryTeamMembers.length} - cls="teamMember" - /> </section> <header> <h2> @@ -2887,10 +2871,6 @@ const TeamMembersPage = memo(() => { </header> <section className="main-text board-members"> <BoardMembers boardMembers={teamBoardMembers} /> - <Placeholders - teamMembersCount={teamBoardMembers.length} - cls="teamBoardMember" - /> </section> </> )} From fd6e824c826e507176c2873d25e48b76f11a8359 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Sun, 19 Nov 2023 16:07:42 -0500 Subject: [PATCH 119/260] Move components and functions related to TeamMembersPage inside the component --- static/js/StaticPages.jsx | 168 +++++++++++++++++++------------------- 1 file changed, 85 insertions(+), 83 deletions(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index b1fc96b5a2..3d1a835b15 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2640,14 +2640,7 @@ const ConditionalLink = ({ link, children }) => * Team Page */ -const byLastName = () => { - const locale = Sefaria.interfaceLang === "hebrew" ? "he" : "en"; - return (a, b) => { - const lastNameA = a.teamMemberDetails.teamName[locale].split(" ").pop(); - const lastNameB = b.teamMemberDetails.teamName[locale].split(" ").pop(); - return lastNameA.localeCompare(lastNameB, locale); - }; -}; + const partition = (arr, prop) => arr.reduce( @@ -2658,88 +2651,97 @@ const partition = (arr, prop) => [[], []] ); -const TeamTitle = ({ teamTitle }) => ( - <div className="teamTitle"> - <InterfaceText text={teamTitle} /> - </div> -); - -const TeamName = ({ teamName }) => ( - <div className="teamName"> - <InterfaceText text={teamName} /> - </div> -); - -const TeamMemberDetails = ({ teamMemberDetails }) => ( - <div className="teamMemberDetails"> - <TeamName teamName={teamMemberDetails.teamName} /> - <TeamTitle teamTitle={teamMemberDetails.teamTitle} /> - </div> -); - -const TeamMemberImage = ({ teamMember }) => ( - <div className="teamMemberImage"> - <img - src={teamMember.teamMemberImage} - alt={`Headshot of ${teamMember.teamMemberDetails.teamName.en}`} - /> - </div> -); - -const TeamMember = ({ teamMember }) => ( - <div className="teamMember"> - <TeamMemberImage teamMember={teamMember} /> - <TeamMemberDetails teamMemberDetails={teamMember.teamMemberDetails} /> - </div> -); - -const TeamMembers = ({ teamMembers }) => ( - <> - {teamMembers.map((teamMember) => ( - <TeamMember key={teamMember.id} teamMember={teamMember} /> - ))} - </> -); - -const BoardMember = ({ boardMember }) => ( - <div className="teamBoardMember"> - <TeamMemberDetails teamMemberDetails={boardMember.teamMemberDetails} /> - </div> -); - -const BoardMembers = ({ boardMembers }) => { - let chairmanBoardMember; - const chairmanIndex = boardMembers.findIndex( - (boardMember) => - boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === - "chairman" +const TeamMembersPage = memo(() => { + const byLastName = () => { + const locale = Sefaria.interfaceLang === "hebrew" ? "he" : "en"; + return (a, b) => { + const lastNameA = a.teamMemberDetails.teamName[locale].split(" ").pop(); + const lastNameB = b.teamMemberDetails.teamName[locale].split(" ").pop(); + return lastNameA.localeCompare(lastNameB, locale); + }; + }; + + const TeamTitle = ({ teamTitle }) => ( + <div className="teamTitle"> + <InterfaceText text={teamTitle} /> + </div> ); - if (chairmanIndex !== -1) { - chairmanBoardMember = boardMembers.splice(chairmanIndex, 1); - } - const [cofounderBoardMembers, regularBoardMembers] = partition( - boardMembers, - (boardMember) => - boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === - "co-founder" + + const TeamName = ({ teamName }) => ( + <div className="teamName"> + <InterfaceText text={teamName} /> + </div> ); - - return ( + + const TeamMemberDetails = ({ teamMemberDetails }) => ( + <div className="teamMemberDetails"> + <TeamName teamName={teamMemberDetails.teamName} /> + <TeamTitle teamTitle={teamMemberDetails.teamTitle} /> + </div> + ); + + const TeamMemberImage = ({ teamMember }) => ( + <div className="teamMemberImage"> + <img + src={teamMember.teamMemberImage} + alt={`Headshot of ${teamMember.teamMemberDetails.teamName.en}`} + /> + </div> + ); + + const TeamMember = ({ teamMember }) => ( + <div className="teamMember"> + <TeamMemberImage teamMember={teamMember} /> + <TeamMemberDetails teamMemberDetails={teamMember.teamMemberDetails} /> + </div> + ); + + const TeamMembers = ({ teamMembers }) => ( <> - {chairmanBoardMember && ( - <BoardMember boardMember={chairmanBoardMember[0]} /> - )} - {cofounderBoardMembers.map((boardMember) => ( - <BoardMember key={boardMember.id} boardMember={boardMember} /> - ))} - {regularBoardMembers.sort(byLastName()).map((boardMember) => ( - <BoardMember key={boardMember.id} boardMember={boardMember} /> + {teamMembers.map((teamMember) => ( + <TeamMember key={teamMember.id} teamMember={teamMember} /> ))} </> ); -}; + + const BoardMember = ({ boardMember }) => ( + <div className="teamBoardMember"> + <TeamMemberDetails teamMemberDetails={boardMember.teamMemberDetails} /> + </div> + ); + + const BoardMembers = ({ boardMembers }) => { + let chairmanBoardMember; + const chairmanIndex = boardMembers.findIndex( + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === + "chairman" + ); + if (chairmanIndex !== -1) { + chairmanBoardMember = boardMembers.splice(chairmanIndex, 1); + } + const [cofounderBoardMembers, regularBoardMembers] = partition( + boardMembers, + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === + "co-founder" + ); + + return ( + <> + {chairmanBoardMember && ( + <BoardMember boardMember={chairmanBoardMember[0]} /> + )} + {cofounderBoardMembers.map((boardMember) => ( + <BoardMember key={boardMember.id} boardMember={boardMember} /> + ))} + {regularBoardMembers.sort(byLastName()).map((boardMember) => ( + <BoardMember key={boardMember.id} boardMember={boardMember} /> + ))} + </> + ); + }; -const TeamMembersPage = memo(() => { const [ordinaryTeamMembers, setOrdinaryTeamMembers] = useState([]); const [teamBoardMembers, setTeamBoardMembers] = useState([]); const [error, setError] = useState(null); From 2cc9808f7398e2041808f84511d683b5cb165a17 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Sun, 19 Nov 2023 16:57:14 -0500 Subject: [PATCH 120/260] feat(jobs): Allow job postings to have a start date and end date for visibility --- static/js/StaticPages.jsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index a933720a79..5ca9555b9a 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2636,6 +2636,10 @@ const StaticHR = () => const ConditionalLink = ({ link, children }) => link ? <a href={link} target="_blank">{children}</a> : children; +/* +* Jobs Page +*/ + const JobsPageHeader = ({ jobsAreAvailable }) => { return ( <> @@ -2750,10 +2754,17 @@ const JobsPage = memo(() => { const [error, setError] = useState(null); useEffect(() => { + const currentDateTime = new Date().toISOString(); const fetchJobsJSON = async () => { const query = ` query { - jobPostings(pagination: { limit: -1 }) { + jobPostings( + pagination: { limit: -1 } + filters: { + jobPostingStartDate: { lte: \"${currentDateTime}\" } + jobPostingEndDate: { gte: \"${currentDateTime}\" } + } + ) { data { id attributes { From b6ab3225c3df226cdec9b24a230d8541307d3cf4 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Sun, 19 Nov 2023 17:11:22 -0500 Subject: [PATCH 121/260] Move components to be within the JobsPage component and rename component name with comments for clearer intention and less confusion --- static/js/StaticPages.jsx | 305 +++++++++++++++++++------------------- 1 file changed, 156 insertions(+), 149 deletions(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 5ca9555b9a..a72ee6948a 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2640,165 +2640,172 @@ const ConditionalLink = ({ link, children }) => * Jobs Page */ -const JobsPageHeader = ({ jobsAreAvailable }) => { - return ( - <> - <header> - <h1 className="serif"> - <span className="int-en">Jobs at Sefaria</span> - <span className="int-he">משרות פנויות בספריא</span> - </h1> - - {jobsAreAvailable ? ( - <> - <h2> - <span className="int-en">About Sefaria</span> - <span className="int-he">אודות ספריא</span> - </h2> - <p> - <span className="int-en"> - Sefaria is a non-profit organization dedicated to creating the - future of Torah in an open and participatory way. We are assembling - a free, living library of Jewish texts and their interconnections, - in Hebrew and in translation. - </span> - <span className="int-he"> - ספריא היא ארגון ללא מטרות רווח שמטרתו יצירת הדור הבא של לימוד התורה - באופן פתוח ומשותף. אנחנו בספריא מרכיבים ספרייה חיה וחופשית של טקסטים - יהודיים וכלל הקישורים ביניהם, בשפת המקור ובתרגומים. - </span> - </p> - </> - ) : null} - </header> - </> - ); -}; - -const Job = ({ job }) => { - return ( - <div className="job"> - <a className="joblink" target="_blank" href={job.jobLink}> - {job.jobDescription} - </a> - </div> - ); -}; - -const JobsListForDepartment = ({ jobsList }) => { - return ( - <section className="jobsListForDepartment"> - {jobsList.map((job) => ( - <Job key={job.id} job={job} /> - ))} - </section> - ); -}; - -const DepartmentJobPostings = ({ department, departmentJobPostings }) => { - return ( - <section className="section department englishOnly"> - <header> - <h2 className="anchorable">{department}</h2> - </header> - <JobsListForDepartment key={department} jobsList={departmentJobPostings} /> - </section> - ); -}; - -const JobPostingsByDepartment = ({ jobPostings }) => { - const groupedJobPostings = jobPostings.reduce((jobPostingsGroupedByDepartment, jobPosting) => { - const category = jobPosting.jobDepartmentCategory; - if (!jobPostingsGroupedByDepartment[category]) { - jobPostingsGroupedByDepartment[category] = []; - } - jobPostingsGroupedByDepartment[category].push(jobPosting); - return jobPostingsGroupedByDepartment; - }, {}); - - return ( - Object.entries(groupedJobPostings).map(([department, departmentJobPostings]) => { - return ( - <DepartmentJobPostings - key={department} - department={department} - departmentJobPostings={departmentJobPostings} - /> - ); - }) - ); -}; - +const JobsPage = memo(() => { -const NoJobsNotice = () => { - return ( - <div className="section nothing"> - <p> - <span className="int-en"> - Sefaria does not currently have any open positions. - Please follow us on <a target="_blank" href="http://www.facebook.com/sefaria.org" >Facebook</a> - to hear about our next openings. - </span> - <span className="int-he"> - ספריא איננה מחפשת כעת עובדים חדשים. - עקבו אחרינו ב<a target="_blank" href="http://www.facebook.com/sefaria.org" >פייסבוק</a>  - כדי להשאר מעודכנים במשרות עתידיות. - </span> - </p> - </div> - ); -}; + // Show a different header with a description of Sefaria for the page in the case that there are jobs available + const JobsPageHeader = ({ jobsAreAvailable }) => { + return ( + <> + <header> + <h1 className="serif"> + <span className="int-en">Jobs at Sefaria</span> + <span className="int-he">משרות פנויות בספריא</span> + </h1> + + {jobsAreAvailable ? ( + <> + <h2> + <span className="int-en">About Sefaria</span> + <span className="int-he">אודות ספריא</span> + </h2> + <p> + <span className="int-en"> + Sefaria is a non-profit organization dedicated to creating the + future of Torah in an open and participatory way. We are assembling + a free, living library of Jewish texts and their interconnections, + in Hebrew and in translation. + </span> + <span className="int-he"> + ספריא היא ארגון ללא מטרות רווח שמטרתו יצירת הדור הבא של לימוד התורה + באופן פתוח ומשותף. אנחנו בספריא מרכיבים ספרייה חיה וחופשית של טקסטים + יהודיים וכלל הקישורים ביניהם, בשפת המקור ובתרגומים. + </span> + </p> + </> + ) : null} + </header> + </> + ); + }; + + const Job = ({ job }) => { + return ( + <div className="job"> + <a className="joblink" target="_blank" href={job.jobLink}> + {job.jobDescription} + </a> + </div> + ); + }; + + // Show the list of job postings within a department category + const JobsListForDepartment = ({ jobsList }) => { + return ( + <section className="jobsListForDepartment"> + {jobsList.map((job) => ( + <Job key={job.id} job={job} /> + ))} + </section> + ); + }; + + // Job postings are grouped by department. This component will show the jobs for a specific department + // Each department has a header for its category before showing a list of the job postings there + const JobPostingsByDepartment = ({ department, departmentJobPostings }) => { + return ( + <section className="section department englishOnly"> + <header> + <h2 className="anchorable">{department}</h2> + </header> + <JobsListForDepartment key={department} jobsList={departmentJobPostings} /> + </section> + ); + }; + + // Show all the job postings, but first group them by department and render each department separately + const JobPostings = ({ jobPostings }) => { + const groupedJobPostings = jobPostings.reduce((jobPostingsGroupedByDepartment, jobPosting) => { + const category = jobPosting.jobDepartmentCategory; + if (!jobPostingsGroupedByDepartment[category]) { + jobPostingsGroupedByDepartment[category] = []; + } + jobPostingsGroupedByDepartment[category].push(jobPosting); + return jobPostingsGroupedByDepartment; + }, {}); + + return ( + Object.entries(groupedJobPostings).map(([department, departmentJobPostings]) => { + return ( + <JobPostingsByDepartment + key={department} + department={department} + departmentJobPostings={departmentJobPostings} + /> + ); + }) + ); + }; + + + const NoJobsNotice = () => { + return ( + <div className="section nothing"> + <p> + <span className="int-en"> + Sefaria does not currently have any open positions. + Please follow us on <a target="_blank" href="http://www.facebook.com/sefaria.org" >Facebook</a> + to hear about our next openings. + </span> + <span className="int-he"> + ספריא איננה מחפשת כעת עובדים חדשים. + עקבו אחרינו ב<a target="_blank" href="http://www.facebook.com/sefaria.org" >פייסבוק</a>  + כדי להשאר מעודכנים במשרות עתידיות. + </span> + </p> + </div> + ); + }; -const JobsPage = memo(() => { const [jobPostings, setJobPostings] = useState([]); const [error, setError] = useState(null); - useEffect(() => { + const fetchJobsJSON = async () => { const currentDateTime = new Date().toISOString(); - const fetchJobsJSON = async () => { - const query = ` - query { - jobPostings( - pagination: { limit: -1 } - filters: { - jobPostingStartDate: { lte: \"${currentDateTime}\" } - jobPostingEndDate: { gte: \"${currentDateTime}\" } - } - ) { - data { - id - attributes { - jobLink - jobDescription - jobDepartmentCategory - } + const query = ` + query { + jobPostings( + pagination: { limit: -1 } + filters: { + jobPostingStartDate: { lte: \"${currentDateTime}\" } + jobPostingEndDate: { gte: \"${currentDateTime}\" } + } + ) { + data { + id + attributes { + jobLink + jobDescription + jobDepartmentCategory } } } - `; - try { - const response = await fetch(STRAPI_INSTANCE + "/graphql", { - method: "POST", - mode: "cors", - cache: "no-cache", - credentials: "omit", - headers: { - "Content-Type": "application/json", - }, - redirect: "follow", - referrerPolicy: "no-referrer", - body: JSON.stringify({ query }), - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.statusText}`); - } - const data = await response.json(); - return data; - } catch (error) { - throw error; } - }; + `; + + try { + const response = await fetch(STRAPI_INSTANCE + "/graphql", { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "omit", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify({ query }), + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + throw error; + } + }; + useEffect(() => { const loadJobPostings = async () => { if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { try { @@ -2836,7 +2843,7 @@ const JobsPage = memo(() => { <> <JobsPageHeader jobsAreAvailable={jobPostings?.length} /> {jobPostings?.length ? ( - <JobPostingsByDepartment jobPostings={jobPostings} /> + <JobPostings jobPostings={jobPostings} /> ) : ( <NoJobsNotice /> )} From 65787d12230a9766bcbfc518414fdd41c307eda8 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Mon, 20 Nov 2023 15:25:51 +0200 Subject: [PATCH 122/260] chore: starting backend for image uploader --- sourcesheets/views.py | 45 +++++++++++++++++--------------- static/js/AdminEditor.jsx | 13 ++-------- static/js/Misc.jsx | 54 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 33 deletions(-) diff --git a/sourcesheets/views.py b/sourcesheets/views.py index d9b0eb80de..7c5fd71444 100644 --- a/sourcesheets/views.py +++ b/sourcesheets/views.py @@ -1134,35 +1134,38 @@ def export_to_drive(request, credential, sheet_id): return jsonResponse(new_file) +def post_img_to_bucket(file, file_name, bucket_name, max_img_size=None): + from PIL import Image + from io import BytesIO + import base64 + import imghdr + img_file_in_mem = BytesIO(base64.b64decode(file)) + + if imghdr.what(img_file_in_mem) == "gif": + img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"{file_name}.gif", bucket_name) + else: + im = Image.open(img_file_in_mem) + img_file = BytesIO() + if max_img_size: + im.thumbnail(max_img_size, Image.ANTIALIAS) + im.save(img_file, format=im.format) + img_file.seek(0) + + img_url = GoogleStorageManager.upload_file(img_file, f"{file_name}.{im.format.lower()}", bucket_name) + return img_url + @catch_error_as_json def upload_sheet_media(request): if not request.user.is_authenticated: return jsonResponse({"error": _("You must be logged in to access this api.")}) if request.method == "POST": - from PIL import Image - from io import BytesIO - import uuid - import base64 - import imghdr - bucket_name = GoogleStorageManager.UGC_SHEET_BUCKET max_img_size = [1024, 1024] - - img_file_in_mem = BytesIO(base64.b64decode(request.POST.get('file'))) - - if imghdr.what(img_file_in_mem) == "gif": - img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"{request.user.id}-{uuid.uuid1()}.gif", bucket_name) - - else: - im = Image.open(img_file_in_mem) - img_file = BytesIO() - im.thumbnail(max_img_size, Image.ANTIALIAS) - im.save(img_file, format=im.format) - img_file.seek(0) - - img_url = GoogleStorageManager.upload_file(img_file, f"{request.user.id}-{uuid.uuid1()}.{im.format.lower()}", bucket_name) - + import uuid + file = request.POST.get('file') + file_name = f'{request.user.id}-{uuid.uuid1()}' + img_url = post_img_to_bucket(file, file_name, bucket_name, max_img_size) return jsonResponse({"url": img_url}) return jsonResponse({"error": "Unsupported HTTP method."}) diff --git a/static/js/AdminEditor.jsx b/static/js/AdminEditor.jsx index 68d0c62903..0f32c72d1f 100644 --- a/static/js/AdminEditor.jsx +++ b/static/js/AdminEditor.jsx @@ -1,6 +1,6 @@ import React, {useRef, useState} from "react"; import Sefaria from "./sefaria/sefaria"; -import {AdminToolHeader, InterfaceText, ProfilePic, TitleVariants} from "./Misc"; +import {AdminToolHeader, InterfaceText, PictureUploader, TitleVariants} from "./Misc"; import sanitizeHtml from 'sanitize-html'; import classNames from "classnames"; const options_for_form = { @@ -163,20 +163,11 @@ const AdminEditor = ({title, data, close, catMenu, updateData, savingStatus, </select> </div>; } - const getPicture = (label, field) => { - return <ProfilePic - saveCallback={(url) => handlePictureChange(url)} - showButtons={true} - name={""} - url={data[field]} - len={60} - />; - } const item = ({label, field, placeholder, type, dropdown_data}) => { let obj; switch(type) { case 'picture': - obj = getPicture(label, field); + obj = <PictureUploader callback={handlePictureChange}/>; break; case 'dropdown': obj = getDropdown(field, dropdown_data, placeholder); diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 7d3aacae68..c0f1f6d30a 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1570,6 +1570,57 @@ FollowButton.propTypes = { }; +const PictureUploader = (callback) => { + const fileInput = useRef(null); + + var uploadImage = function(imageData) { + const formData = new FormData(); + formData.append('file', imageData.replace(/data:image\/(jpe?g|png|gif);base64,/, "")); + // formData.append('file', imageData); + + $.ajax({ + url: Sefaria.apiHost + "/api/sheets/upload-image", + type: 'POST', + data: formData, + contentType: false, + processData: false, + success: function(data) { + callback(data.url); + // $("#inlineAddMediaInput").val(data.url); + // $("#addmediaDiv").find(".button").first().trigger("click"); + // $("#inlineAddMediaInput").val(""); + }, + error: function(e) { + console.log("photo upload ERROR", e); + } + }); + } + const onFileSelect = (e) => { + const file = fileInput.current.files[0]; + if (file == null) + return; + if (/\.(jpe?g|png|gif)$/i.test(file.name)) { + const reader = new FileReader(); + + reader.addEventListener("load", function() { + uploadImage(reader.result); + }, false); + + reader.addEventListener("onerror", function() { + alert(reader.error); + }, false); + + reader.readAsDataURL(file); + } else { + alert('not an image'); + } + } + return <div> + <div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton"> + <label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"></label> + </div><input id="addImageFileSelector" type="file" style={{ display: "none"}} onChange={onFileSelect} ref={fileInput} /> + </div>; + } const CategoryColorLine = ({category}) => <div className="categoryColorLine" style={{background: Sefaria.palette.categoryColor(category)}}/>; @@ -3243,5 +3294,6 @@ export { CategoryChooser, TitleVariants, requestWithCallBack, - OnInView + OnInView, + PictureUploader }; From 1596f1810a93d6a09b12ab12cab7d366283204db Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 20 Nov 2023 15:42:19 +0200 Subject: [PATCH 123/260] feat(api-v3): add new attribute 'fullLanguage' to Version, and use it in the api rather than actualLanguage which is just the language ISO code. --- api/api_warnings.py | 4 ++-- api/tests.py | 26 +++++++++++++------------- sefaria/constants/model.py | 20 ++++++++++++++++++++ sefaria/model/text.py | 10 ++++++++-- sefaria/model/text_manager.py | 10 +++++----- 5 files changed, 48 insertions(+), 22 deletions(-) diff --git a/api/api_warnings.py b/api/api_warnings.py index aecf32b601..ac4a4e4788 100644 --- a/api/api_warnings.py +++ b/api/api_warnings.py @@ -38,14 +38,14 @@ class APINoVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, vtitle: str, lang: str): self.warning_code = APIWarningCode.APINoVersion - self.message = f'We do not have version named {vtitle} with language code {lang} for {oref}' + self.message = f'We do not have version named {vtitle} with language {lang} for {oref}' class APINoLanguageVersion(TextsAPIResponseMessage): def __init__(self, oref: Ref, langs: List[str]): self.warning_code = APIWarningCode.APINoLanguageVersion - self.message = f'We do not have the code language you asked for {oref}. Available codes are {langs}' + self.message = f'We do not have the language you asked for {oref}. Available languages are {langs}' class APINoSourceText(TextsAPIResponseMessage): diff --git a/api/tests.py b/api/tests.py index 78549980f8..9b60202875 100644 --- a/api/tests.py +++ b/api/tests.py @@ -59,7 +59,7 @@ def test_api_get_text_translation(self): self.assertEqual(data["versions"][0]['versionTitle'], "William Davidson Edition - English") def test_api_get_text_lang_all(self): - response = c.get('/api/v3/texts/Rashi_on_Genesis.2.3?version=en|all') + response = c.get('/api/v3/texts/Rashi_on_Genesis.2.3?version=english|all') self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertTrue(len(data["versions"]) > 1) @@ -71,7 +71,7 @@ def test_api_get_text_lang_all(self): self.assertEqual(data["toSections"], ['2', '3']) def test_api_get_text_specific(self): - response = c.get('/api/v3/texts/Tosafot_on_Sukkah.2a.4.1?version=he|Vilna_Edition') + response = c.get('/api/v3/texts/Tosafot_on_Sukkah.2a.4.1?version=hebrew|Vilna_Edition') self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) @@ -97,7 +97,7 @@ def test_api_get_text_base(self): self.assertEqual(data["versions"][0]['versionTitle'], "William Davidson Edition - Vocalized Aramaic") def test_api_get_text_two_params(self): - response = c.get('/api/v3/texts/Genesis.1?version=he|Tanach with Nikkud&version=en|all') + response = c.get('/api/v3/texts/Genesis.1?version=hebrew|Tanach with Nikkud&version=english|all') data = json.loads(response.content) self.assertTrue(len(data["versions"]) > 7) self.assertEqual(data["versions"][0]['actualLanguage'], 'he') @@ -146,7 +146,7 @@ def test_api_get_text_empty_ref(self): self.assertEqual(data["error"], "We have no text for Berakhot 1a.") def test_api_get_text_no_source(self): - response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=en|Brenton's_Septuagint&version=source") + response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=english|Brenton's_Septuagint&version=source") self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) @@ -162,26 +162,26 @@ def test_api_get_text_no_translation(self): self.assertEqual(data['warnings'][0]['translation']['message'], 'We do not have a translation for Shuvi Shuvi HaShulamit') def test_api_get_text_no_language(self): - response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=en|Brenton's_Septuagint&version=sgrg|all") + response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=english|Brenton's_Septuagint&version=sgrg|all") self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) self.assertEqual(data['warnings'][0]['sgrg|all']['warning_code'], APIWarningCode.APINoLanguageVersion.value) self.assertEqual(data['warnings'][0]['sgrg|all']['message'], - "We do not have the code language you asked for The Book of Maccabees I 1. Available codes are ['en', 'he']") + "We do not have the language you asked for The Book of Maccabees I 1. Available languages are ['english', 'hebrew']") def test_api_get_text_no_version(self): - response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=en|Brenton's_Septuagint&version=he|Kishkoosh") + response = c.get("/api/v3/texts/The_Book_of_Maccabees_I.1?version=english|Brenton's_Septuagint&version=hebrew|Kishkoosh") self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) - self.assertEqual(data['warnings'][0]['he|Kishkoosh']['warning_code'], APIWarningCode.APINoVersion.value) - self.assertEqual(data['warnings'][0]['he|Kishkoosh']['message'], - 'We do not have version named Kishkoosh with language code he for The Book of Maccabees I 1') + self.assertEqual(data['warnings'][0]['hebrew|Kishkoosh']['warning_code'], APIWarningCode.APINoVersion.value) + self.assertEqual(data['warnings'][0]['hebrew|Kishkoosh']['message'], + 'We do not have version named Kishkoosh with language hebrew for The Book of Maccabees I 1') def test_fill_in_missing_segments(self): vtitle = "Maimonides' Mishneh Torah, edited by Philip Birnbaum, New York, 1967" - response = c.get(f"/api/v3/texts/Mishneh_Torah,_Sabbath_1?version=en|{vtitle}&fill_in_missing_segments=true") + response = c.get(f"/api/v3/texts/Mishneh_Torah,_Sabbath_1?version=english|{vtitle}&fill_in_missing_segments=true") self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertTrue(len(data['versions'][0]['text']) > 2) @@ -191,7 +191,7 @@ def test_fill_in_missing_segments(self): def test_without_fill_in_missing_segments(self): vtitle = "Maimonides' Mishneh Torah, edited by Philip Birnbaum, New York, 1967" - response = c.get(f"/api/v3/texts/Mishneh_Torah,_Sabbath_1?version=en|{vtitle}") + response = c.get(f"/api/v3/texts/Mishneh_Torah,_Sabbath_1?version=english|{vtitle}") self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data['versions'][0]['text']), 2) @@ -199,7 +199,7 @@ def test_without_fill_in_missing_segments(self): def test_wrap_all_entities(self): vtitle = "The Contemporary Torah, Jewish Publication Society, 2006" - response = c.get(f"/api/v3/texts/Genesis%2010?version=en|{vtitle}&return_format=wrap_all_entities") + response = c.get(f"/api/v3/texts/Genesis%2010?version=english|{vtitle}&return_format=wrap_all_entities") self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertTrue('<a class ="refLink"' in data['versions'][0]['text'][3]) diff --git a/sefaria/constants/model.py b/sefaria/constants/model.py index 107e208a45..8c681d9a02 100644 --- a/sefaria/constants/model.py +++ b/sefaria/constants/model.py @@ -10,3 +10,23 @@ 'img': ['src', 'alt'], 'a': ['dir', 'class', 'href', 'data-ref', "data-ven", "data-vhe", 'data-scroll-link'], } + +LANGUAGE_CODES = { + #maps ISO language codes to their nother language (i.e. jrb to Arabic rather than Judeo-Arabic) + "ar": "arabic", + "de": "german", + "en": "english", + "eo": "esperanto", + "es": "spanish", + "fa": "persian", + "fi": "finnish", + "fr": "french", + "he": "hebrew", + "it": "italian", + "lad": "ladino", + "pl": "polish", + "pt": "portuguese", + "ru": "russian", + "yi": "yiddish", + "jrb": "arabic", +} diff --git a/sefaria/model/text.py b/sefaria/model/text.py index f065d30756..50ce59e93b 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1335,6 +1335,7 @@ class Version(AbstractTextRecord, abst.AbstractMongoRecord, AbstractSchemaConten "purchaseInformationURL", "hasManuallyWrappedRefs", # true for texts where refs were manually wrapped in a-tags. no need to run linker at run-time. "actualLanguage", + 'fullLanguage', "isBaseText", 'isSource', 'isPrimary', @@ -1356,6 +1357,11 @@ def _validate(self): languageCodeRe = re.search(r"\[([a-z]{2})\]$", getattr(self, "versionTitle", None)) if languageCodeRe and languageCodeRe.group(1) != getattr(self,"actualLanguage",None): self.actualLanguage = languageCodeRe.group(1) + if not getattr(self, 'fullLanguage', None): + try: + self.fullLanguage = constants.LANGUAGE_CODES[self.actualLanguage] + except KeyError: + self.fullLanguage = constants.LANGUAGE_CODES[self.language] if getattr(self,"language", None) not in ["en", "he"]: raise InputError("Version language must be either 'en' or 'he'") index = self.get_index() @@ -1709,7 +1715,7 @@ def __init__(self, oref, lang, vtitle, merge_versions=False): def versions(self): if self._versions == []: condition_query = self.oref.condition_query(self.lang) if self.merge_versions else \ - {'title': self.oref.index.title, 'actualLanguage': self.lang, 'versionTitle': self.vtitle} + {'title': self.oref.index.title, 'fullLanguage': self.lang, 'versionTitle': self.vtitle} self._versions = VersionSet(condition_query, proj=self.oref.part_projection()) return self._versions @@ -1722,7 +1728,7 @@ def _validate_versions(self, versions): if not self.merge_versions and len(versions) > 1: raise InputError("Got many versions instead of one") for version in versions: - condition = version.title == self.oref.index.title and version.actualLanguage == self.lang + condition = version.title == self.oref.index.title and version.fullLanguage == self.lang if not self.merge_versions: condition = condition and version.versionTitle == self.vtitle if not condition: diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index c4fb8c4f23..7b9a3bba06 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -28,7 +28,7 @@ def __init__(self, oref: Ref, versions_params: List[List[str]], fill_in_missing_ self.return_obj = { 'versions': [], 'missings': [], - 'available_langs': sorted({v.actualLanguage for v in self.all_versions}), + 'available_langs': sorted({v.fullLanguage for v in self.all_versions}), 'available_versions': [{f: getattr(v, f, "") for f in fields} for v in self.all_versions] } @@ -38,12 +38,12 @@ def _append_version(self, version): for attr in ['chapter', 'title', 'language']: fields.remove(attr) version_details = {f: getattr(version, f, "") for f in fields} - text_range = TextRange(self.oref, version.actualLanguage, version.versionTitle, self.fill_in_missing_segments) + text_range = TextRange(self.oref, version.fullLanguage, version.versionTitle, self.fill_in_missing_segments) if self.fill_in_missing_segments: # we need a new VersionSet of only the relevant versions for merging. copy should be better than calling for mongo relevant_versions = copy.copy(self.all_versions) - relevant_versions.remove(lambda v: v.actualLanguage != version.actualLanguage) + relevant_versions.remove(lambda v: v.fullLanguage != version.fullLanguage) else: relevant_versions = [version] text_range.versions = relevant_versions @@ -66,7 +66,7 @@ def _append_required_versions(self, lang: str, vtitle: str) -> None: elif lang == self.TRANSLATION: lang_condition = lambda v: not getattr(v, 'isSource', False) elif lang: - lang_condition = lambda v: v.actualLanguage == lang + lang_condition = lambda v: v.fullLanguage == lang else: lang_condition = lambda v: True if vtitle and vtitle != self.ALL: @@ -76,7 +76,7 @@ def _append_required_versions(self, lang: str, vtitle: str) -> None: if vtitle != self.ALL and versions: versions = [max(versions, key=lambda v: getattr(v, 'priority', 0))] for version in versions: - if all(version.actualLanguage != v['actualLanguage'] or version.versionTitle != v['versionTitle'] for v in self.return_obj['versions']): + if all(version.fullLanguage != v['fullLanguage'] or version.versionTitle != v['versionTitle'] for v in self.return_obj['versions']): #do not return the same version even if included in two different version params self._append_version(version) if not versions: From 8115d1f428717c8474c672ee1072a2bf635b6b01 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 20 Nov 2023 15:51:13 +0200 Subject: [PATCH 124/260] feat(api-v3): return error when return_format is not one of our known formats. --- api/tests.py | 7 ++++++- api/views.py | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/api/tests.py b/api/tests.py index 9b60202875..1ea31d542b 100644 --- a/api/tests.py +++ b/api/tests.py @@ -205,9 +205,14 @@ def test_wrap_all_entities(self): self.assertTrue('<a class ="refLink"' in data['versions'][0]['text'][3]) self.assertTrue('<a href="/topics' in data['versions'][0]['text'][8]) - def text_text_only(self): + def test_text_only(self): response = c.get(f"/api/v3/texts/Shulchan_Arukh%2C_Orach_Chayim.1:1?return_format=text_only") self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertFalse('<' in data['versions'][0]['text']) + def test_error_return_format(self): + response = c.get(f"/api/v3/texts/Shulchan_Arukh%2C_Orach_Chayim.1:1?return_format=not_valid") + self.assertEqual(400, response.status_code) + data = json.loads(response.content) + self.assertEqual(data['error'], "return_format should be one of those formats: ['default', 'wrap_all_entities', 'text_only'].") diff --git a/api/views.py b/api/views.py index ec2f67077f..c6e0beca82 100644 --- a/api/views.py +++ b/api/views.py @@ -7,6 +7,8 @@ class Text(View): + RETURN_FORMATS = ['default', 'wrap_all_entities', 'text_only'] + def dispatch(self, request, *args, **kwargs): try: self.oref = Ref.instantiate_ref_with_legacy_parse_fallback(kwargs['tref']) @@ -48,6 +50,8 @@ def get(self, request, *args, **kwargs): versions_params = [self.split_piped_params(param_str) for param_str in versions_params] fill_in_missing_segments = request.GET.get('fill_in_missing_segments', False) return_format = request.GET.get('return_format', 'default') + if return_format not in self.RETURN_FORMATS: + return jsonResponse({'error': f'return_format should be one of those formats: {self.RETURN_FORMATS}.'}, status=400) text_manager = TextManager(self.oref, versions_params, fill_in_missing_segments, return_format) data = text_manager.get_versions_for_query() data = self._handle_warnings(data) From 778cf492c5ea08db2f1cdfba0d4edf6cac4579f9 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Tue, 21 Nov 2023 09:31:59 +0200 Subject: [PATCH 125/260] chore: start topic_upload_photo --- reader/views.py | 18 +++++++++++++++++ sefaria/urls.py | 1 + sourcesheets/views.py | 46 ++++++++++++++++++++----------------------- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/reader/views.py b/reader/views.py index b93cc2bd1e..5a2b550460 100644 --- a/reader/views.py +++ b/reader/views.py @@ -3567,6 +3567,24 @@ def profile_follow_api(request, ftype, slug): return jsonResponse(response) return jsonResponse({"error": "Unsupported HTTP method."}) +@catch_error_as_json +def topic_upload_photo(request): + if not request.user.is_authenticated: + return jsonResponse({"error": _("You must be logged in to update your profile photo.")}) + if request.method == "POST": + now = epoch_time() + + profile = UserProfile(id=request.user.id) + bucket_name = GoogleStorageManager.PROFILES_BUCKET + image = Image.open(request.FILES['file']) + old_big_pic_filename = GoogleStorageManager.get_filename_from_url(profile.profile_pic_url) + big_pic_url = GoogleStorageManager.upload_file(get_resized_file(image, (250, 250)), "{}-{}.png".format(profile.slug, now), bucket_name, old_big_pic_filename) + + profile.update({"profile_pic_url": big_pic_url}) + profile.save() + public_user_data(request.user.id, ignore_cache=True) # reset user data cache + return jsonResponse({"url": big_pic_url}) + return jsonResponse({"error": "Unsupported HTTP method."}) @catch_error_as_json def profile_upload_photo(request): diff --git a/sefaria/urls.py b/sefaria/urls.py index 757fcef1aa..ba5be292da 100644 --- a/sefaria/urls.py +++ b/sefaria/urls.py @@ -103,6 +103,7 @@ url(r'^topics/b/(?P<topic>.+)$', reader_views.topic_page_b), url(r'^topics/(?P<topic>.+)$', reader_views.topic_page), url(r'^api/topic/completion/(?P<topic>.+)', reader_views.topic_completion_api) + url(r'^topics/upload-image$', reader_views.topic_upload_photo), ] # Calendar Redirects diff --git a/sourcesheets/views.py b/sourcesheets/views.py index 7c5fd71444..8f63f65762 100644 --- a/sourcesheets/views.py +++ b/sourcesheets/views.py @@ -1134,38 +1134,34 @@ def export_to_drive(request, credential, sheet_id): return jsonResponse(new_file) -def post_img_to_bucket(file, file_name, bucket_name, max_img_size=None): - from PIL import Image - from io import BytesIO - import base64 - import imghdr - img_file_in_mem = BytesIO(base64.b64decode(file)) - - if imghdr.what(img_file_in_mem) == "gif": - img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"{file_name}.gif", bucket_name) - else: - im = Image.open(img_file_in_mem) - img_file = BytesIO() - if max_img_size: - im.thumbnail(max_img_size, Image.ANTIALIAS) - im.save(img_file, format=im.format) - img_file.seek(0) - - img_url = GoogleStorageManager.upload_file(img_file, f"{file_name}.{im.format.lower()}", bucket_name) - return img_url - - @catch_error_as_json def upload_sheet_media(request): if not request.user.is_authenticated: return jsonResponse({"error": _("You must be logged in to access this api.")}) if request.method == "POST": + from PIL import Image + from io import BytesIO + import uuid + import base64 + import imghdr + bucket_name = GoogleStorageManager.UGC_SHEET_BUCKET max_img_size = [1024, 1024] - import uuid - file = request.POST.get('file') - file_name = f'{request.user.id}-{uuid.uuid1()}' - img_url = post_img_to_bucket(file, file_name, bucket_name, max_img_size) + + img_file_in_mem = BytesIO(base64.b64decode(request.POST.get('file'))) + + if imghdr.what(img_file_in_mem) == "gif": + img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"{request.user.id}-{uuid.uuid1()}.gif", bucket_name) + + else: + im = Image.open(img_file_in_mem) + img_file = BytesIO() + im.thumbnail(max_img_size, Image.ANTIALIAS) + im.save(img_file, format=im.format) + img_file.seek(0) + + img_url = GoogleStorageManager.upload_file(img_file, f"{request.user.id}-{uuid.uuid1()}.{im.format.lower()}", bucket_name) + return jsonResponse({"url": img_url}) return jsonResponse({"error": "Unsupported HTTP method."}) From 80f1a8c36672bf554e29ce232a25250612c1c5e6 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Tue, 21 Nov 2023 14:51:10 +0200 Subject: [PATCH 126/260] feat(Topic Editor): Topic Editor can post to google cloud and return url --- reader/views.py | 35 +++++++++++++++++++------------ sefaria/google_storage_manager.py | 6 ++++-- sefaria/urls.py | 4 ++-- sites/sefaria/site_settings.py | 3 ++- static/js/Misc.jsx | 21 ++++++++++++------- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/reader/views.py b/reader/views.py index 5a2b550460..951667996d 100644 --- a/reader/views.py +++ b/reader/views.py @@ -62,7 +62,7 @@ from sefaria.helper.topic import get_topic, get_all_topics, get_topics_for_ref, get_topics_for_book, \ get_bulk_topics, recommend_topics, get_top_topic, get_random_topic, \ get_random_topic_source, edit_topic_source, \ - update_order_of_topic_sources, delete_ref_topic_link, update_authors_place_and_time + update_order_of_topic_sources, delete_ref_topic_link, update_authors_place_and_time, add_image_to_topic from sefaria.helper.community_page import get_community_page_items from sefaria.helper.file import get_resized_file from sefaria.image_generator import make_img_http_response @@ -3572,18 +3572,27 @@ def topic_upload_photo(request): if not request.user.is_authenticated: return jsonResponse({"error": _("You must be logged in to update your profile photo.")}) if request.method == "POST": - now = epoch_time() - - profile = UserProfile(id=request.user.id) - bucket_name = GoogleStorageManager.PROFILES_BUCKET - image = Image.open(request.FILES['file']) - old_big_pic_filename = GoogleStorageManager.get_filename_from_url(profile.profile_pic_url) - big_pic_url = GoogleStorageManager.upload_file(get_resized_file(image, (250, 250)), "{}-{}.png".format(profile.slug, now), bucket_name, old_big_pic_filename) - - profile.update({"profile_pic_url": big_pic_url}) - profile.save() - public_user_data(request.user.id, ignore_cache=True) # reset user data cache - return jsonResponse({"url": big_pic_url}) + from io import BytesIO + import uuid + import base64 + """ + "image" : { + "image_uri" : "https://storage.googleapis.com/img.sefaria.org/topics/shabbat.jpg", + "image_caption" : { + "en" : "Friday Evening, Isidor Kaufmann, Austria c. 1920. The Jewish Museum, Gift of Mr. and Mrs. M. R. Schweitzer", + "he" : "שישי בערב, איזידור קאופמן, וינה 1920. המוזיאון היהודי בניו יורק, מתנת מר וגברת מ.ר. שוויצר" + } + } +Validation that the image_uri url should start with https://storage.googleapis.com/img.sefaria.org/topics/ + """ + bucket_name = GoogleStorageManager.TOPICS_BUCKET + img_file_in_mem = BytesIO(base64.b64decode(request.POST.get('file'))) + img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"topics/{request.user.id}-{uuid.uuid1()}.gif", + bucket_name) + #big_pic_url = GoogleStorageManager.upload_file(get_resized_file(image, (250, 250)), "{}-{}.png".format(profile.slug, now), bucket_name, old_big_pic_filename) + + add_image_to_topic(topic_slug, img_url, en_caption, he_caption) + return jsonResponse({"url": img_url}) return jsonResponse({"error": "Unsupported HTTP method."}) @catch_error_as_json diff --git a/sefaria/google_storage_manager.py b/sefaria/google_storage_manager.py index b186430d6d..4bd2fa6c23 100644 --- a/sefaria/google_storage_manager.py +++ b/sefaria/google_storage_manager.py @@ -18,14 +18,16 @@ class GoogleStorageManager(object): COLLECTIONS_BUCKET = SITE_SETTINGS["COLLECTIONS_BUCKET"] PROFILES_BUCKET = SITE_SETTINGS["PROFILES_BUCKET"] UGC_SHEET_BUCKET = SITE_SETTINGS["UGC_BUCKET"] + TOPICS_BUCKET = SITE_SETTINGS["TOPICS_BUCKET"] BASE_URL = "https://storage.googleapis.com" @classmethod def get_bucket(cls, bucket_name): if getattr(cls, 'client', None) is None: - # for local development, change below line to cls.client = storage.Client(project="production-deployment") - cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) + # for local development, change below line to + cls.client = storage.Client(project="production-deployment") + #cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) bucket = cls.client.get_bucket(bucket_name) return bucket diff --git a/sefaria/urls.py b/sefaria/urls.py index ba5be292da..929467238c 100644 --- a/sefaria/urls.py +++ b/sefaria/urls.py @@ -102,8 +102,8 @@ url(r'^topics/?$', reader_views.topics_page), url(r'^topics/b/(?P<topic>.+)$', reader_views.topic_page_b), url(r'^topics/(?P<topic>.+)$', reader_views.topic_page), - url(r'^api/topic/completion/(?P<topic>.+)', reader_views.topic_completion_api) - url(r'^topics/upload-image$', reader_views.topic_upload_photo), + url(r'^api/topic/completion/(?P<topic>.+)', reader_views.topic_completion_api), + url(r'^api/topics/upload-image$', reader_views.topic_upload_photo), ] # Calendar Redirects diff --git a/sites/sefaria/site_settings.py b/sites/sefaria/site_settings.py index 4f03a3d2df..17ec60b3d9 100644 --- a/sites/sefaria/site_settings.py +++ b/sites/sefaria/site_settings.py @@ -13,5 +13,6 @@ "SUPPORTED_TRANSLATION_LANGUAGES": ['en', 'es', 'fr', 'de'], "COLLECTIONS_BUCKET": "sefaria-collection-images", "PROFILES_BUCKET": 'sefaria-profile-pictures', - "UGC_BUCKET": 'sheet-user-uploaded-media' + "UGC_BUCKET": 'sheet-user-uploaded-media', + "TOPICS_BUCKET": 'img.sefaria.org' } \ No newline at end of file diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 5dea36be55..e0a820257b 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1587,7 +1587,7 @@ const PictureUploader = (callback) => { // formData.append('file', imageData); $.ajax({ - url: Sefaria.apiHost + "/api/sheets/upload-image", + url: Sefaria.apiHost + "/api/topics/upload-image", type: 'POST', data: formData, contentType: false, @@ -1623,11 +1623,18 @@ const PictureUploader = (callback) => { alert('not an image'); } } - return <div> - <div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton"> - <label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"></label> - </div><input id="addImageFileSelector" type="file" style={{ display: "none"}} onChange={onFileSelect} ref={fileInput} /> - </div>; + return <div><div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton"> + <label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"></label> + </div><input id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} /> + <div className="section"> + <label><InterfaceText>English Caption</InterfaceText></label> + <input type="text" id="enCaption"/> + </div> + <div className="section"> + <label><InterfaceText>Hebrew Caption</InterfaceText></label> + <input type="text" id="heCaption"/> + </div> + </div> } const CategoryColorLine = ({category}) => @@ -3313,6 +3320,6 @@ export { TitleVariants, requestWithCallBack, OnInView, - PictureUploader + PictureUploader, ImageWithCaption }; From 9c2829e16f509786da4c2f17d79e9fcda88ab910 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Tue, 21 Nov 2023 15:43:34 +0200 Subject: [PATCH 127/260] refactor(translations): new js class in VersionBlock.jsx for versions handling. new components VersionBlockHeader and VersionBlockSelectButton taken out from VersionBlock. --- static/js/OpenVersion.jsx | 75 ----------------------- static/js/VersionBlock.jsx | 83 +++++++++++++++++++------- static/js/VersionBlockHeader.jsx | 57 ++++++++++++++++++ static/js/VersionBlockSelectButton.jsx | 20 +++++++ 4 files changed, 138 insertions(+), 97 deletions(-) delete mode 100644 static/js/OpenVersion.jsx create mode 100644 static/js/VersionBlockHeader.jsx create mode 100644 static/js/VersionBlockSelectButton.jsx diff --git a/static/js/OpenVersion.jsx b/static/js/OpenVersion.jsx deleted file mode 100644 index d31c061493..0000000000 --- a/static/js/OpenVersion.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import Sefaria from "./sefaria/sefaria"; -import PropTypes from "prop-types"; - -function OpenVersion({currRef, version, currObjectVersions, targetPanel, text, className, openVersionInSidebar, openVersionInReader, rendermode, firstSectionRef}) { - - const mainPanel = targetPanel === 'main'; - function makeVersionLink() { - // maintain all versions for languages you're not currently selecting - if (version.merged) { - return "#"; // there's no url for a merged version - } - const withParam = mainPanel ? "" : "&with=Translation Open"; - const versionParam = mainPanel ? version.language : 'side'; - const nonSelectedVersionParams = Object.entries(currObjectVersions) - .filter(([vlang, ver]) => !!ver && !!ver?.versionTitle && !version?.merged && (withParam || vlang !== version.language)) // in 'side' case, keep all version params - .map(([vlang, ver]) => `&v${vlang}=${ver.versionTitle.replace(/\s/g,'_')}`) - .join(""); - const versionLink = nonSelectedVersionParams === "" ? null : `/${Sefaria.normRef(currRef)}${nonSelectedVersionParams}&v${versionParam}=${version.versionTitle.replace(/\s/g,'_')}${withParam}`.replace("&","?"); - return versionLink; - } - - function openInSidebar(e) { - e.preventDefault(); - try { - gtag("event", "onClick_version_title", {element_name: `version_title`, - change_to: `${version.versionTitle}`, change_from: `${currObjectVersions[version.language]['versionTitle']}`, - categories: `${Sefaria.refCategories(currRef)}`, book: `${Sefaria.parseRef(currRef).index}` }) - } - catch(err) { - console.log(err); - } - openVersionInSidebar(version.versionTitle, version.language); - } - - function openInMainPanel(e) { - e.preventDefault(); - try { - gtag("event", "onClick_select_version", {element_name: `select_version`, - change_to: `${version.versionTitle}`, change_from: `${currObjectVersions[version.language]['versionTitle']}`, - categories: `${Sefaria.refCategories(currRef)}`, book: `${Sefaria.parseRef(currRef).index}` }) - } - catch(err) { - console.log(err); - } - if (rendermode === 'book-page') { - window.location = `/${firstSectionRef}?v${version.language}=${version.versionTitle.replace(/\s/g,'_')}`; - } else { - openVersionInReader(version.versionTitle, version.language); - } - Sefaria.setVersionPreference(currRef, version.versionTitle, version.language); - } - - return ( - <a className={className} - href={makeVersionLink()} - onClick={mainPanel ? openInMainPanel : openInSidebar}> - {text} - </a> - ); -} -OpenVersion.prototypes = { - version: PropTypes.object.isRequired, - currObjectVersions: PropTypes.object.isRequired, - currRef: PropTypes.string.isRequired, - className: PropTypes.string, - openVersionInSidebar: PropTypes.func, - openVersionInReader: PropTypes.func, - targetPanel: PropTypes.string.isRequired, - text: PropTypes.string, - rendermode: PropTypes.string.isRequired, - firstSectionRef: PropTypes.string, -} - -export default OpenVersion; diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index 573b555282..cf3a2a397e 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -6,9 +6,53 @@ import Util from './sefaria/util'; import $ from './sefaria/sefariaJquery'; import Component from 'react-class'; import {LoadingMessage} from "./Misc"; -import OpenVersion from "./OpenVersion"; - +import VersionBlockHeader from "./VersionBlockHeader"; +import VersionBlockSelectButton from "./VersionBlockSelectButton"; +class versionTools { + static makeVersionLink(currRef, version, currObjectVersions, mainPanel) { + if (version.merged) { + return "#"; // there's no url for a merged version + } + const withParam = mainPanel ? "" : "&with=Translation Open"; + const versionParam = mainPanel ? version.language : 'side'; + const nonSelectedVersionParams = Object.entries(currObjectVersions) + .filter(([vlang, ver]) => !!ver && !!ver?.versionTitle && !version?.merged && (withParam || vlang !== version.language)) // in 'side' case, keep all version params + .map(([vlang, ver]) => `&v${vlang}=${ver.versionTitle.replace(/\s/g,'_')}`) + .join(""); + const versionLink = nonSelectedVersionParams === "" ? null : `/${Sefaria.normRef(currRef)}${nonSelectedVersionParams}&v${versionParam}=${version.versionTitle.replace(/\s/g,'_')}${withParam}`.replace("&","?"); + return versionLink; + } + static openVersionInSidebar(currRef, version, currObjectVersions, openVersionInSidebar, e) { + e.preventDefault(); + try { + gtag("event", "onClick_version_title", {element_name: `version_title`, + change_to: `${version.versionTitle}`, change_from: `${currObjectVersions[version.language]['versionTitle']}`, + categories: `${Sefaria.refCategories(currRef)}`, book: `${Sefaria.parseRef(currRef).index}` }) + } + catch(err) { + console.log(err); + } + openVersionInSidebar(version.versionTitle, version.language); + } + static openVersionInMoinPanel(currRef, version, currObjectVersions, renderMode, firstSectionRef, openVersionInReader, e) { + e.preventDefault(); + try { + gtag("event", "onClick_select_version", {element_name: `select_version`, + change_to: `${version.versionTitle}`, change_from: `${currObjectVersions[version.language]['versionTitle']}`, + categories: `${Sefaria.refCategories(currRef)}`, book: `${Sefaria.parseRef(currRef).index}` }) + } + catch(err) { + console.log(err); + } + if (renderMode === 'book-page') { + window.location = `/${firstSectionRef}?v${version.language}=${version.versionTitle.replace(/\s/g,'_')}`; + } else { + openVersionInReader(version.versionTitle, version.language); + } + Sefaria.setVersionPreference(currRef, version.versionTitle, version.language); + } +} class VersionBlock extends Component { constructor(props) { @@ -164,6 +208,10 @@ class VersionBlock extends Component { const vtitle = this.makeVersionTitle(); const vnotes = this.makeVersionNotes(); const showLanguagLabel = this.props.rendermode == "book-page"; + const openVersionInSidebar = versionTools.openVersionInSidebar.bind(null, this.props.currentRef, this.props.version, + this.props.currObjectVersions, this.props.openVersionInSidebar); + const openVersionInMoinPanel = versionTools.openVersionInMoinPanel.bind(null, this.props.currentRef, + this.props.version, this.props.currObjectVersions, this.props.renderMode, this.props.firstSectionRef, this.props.openVersionInReader); if (this.state.editing && Sefaria.is_moderator) { // Editing View @@ -233,33 +281,24 @@ class VersionBlock extends Component { <div className="versionBlock"> <div className="versionBlockHeading"> <div className="versionTitle" role="heading"> - <OpenVersion - version={this.props.version} - currRef={this.props.currentRef} - currObjectVersions={this.props.currObjectVersions} - className={vtitle["className"]} - openVersionInSidebar={this.props.openVersionInSidebar} - openVersionInReader={this.props.openVersionInReader} - targetPanel={this.props.rendermode === 'book-page' ? 'main' : 'side'} + <VersionBlockHeader text={vtitle["text"]} - rendermode={this.props.rendermode} - firstSectionRef={this.props.firstSectionRef} + onClick={this.props.rendermode === 'book-page' ? openVersionInMoinPanel : openVersionInSidebar} + renderMode='versionTitle' + link={versionTools.makeVersionLink(this.props.currentRef, this.props.version, + this.props.currObjectVersions, this.props.rendermode === 'book-page')} /> </div> <i className={`fa fa-pencil versionEditIcon ${(Sefaria.is_moderator && this.props.rendermode == "book-page") ? "enabled" : ""}`} aria-hidden="true" onClick={this.openEditor}/> <div className="versionLanguage sans-serif">{showLanguagLabel ? Sefaria._(Sefaria.translateISOLanguageCode(v.actualLanguage)) : ""}</div> </div> <div className="versionSelect sans-serif"> - <OpenVersion - version={this.props.version} - currRef={this.props.currentRef} - currObjectVersions={this.props.currObjectVersions} - className={`selectButton ${this.props.isCurrent ? "currSelectButton" : ""}`} - openVersionInSidebar={this.props.openVersionInSidebar} - openVersionInReader={this.props.openVersionInReader} - targetPanel='main' - string={this.makeSelectVersionLanguage()} - rendermode={this.props.rendermode} + <VersionBlockSelectButton + isSelected={this.props.isCurrent} + openVersionInMoinPanel={openVersionInMoinPanel} + text={this.makeSelectVersionLanguage()} + link={versionTools.makeVersionLink(this.props.currentRef, this.props.version, + this.props.currObjectVersions, true)} /> </div> <div className={classNames(this.makeAttrClassNames({"versionNotes": 1, "sans-serif": (this.props.rendermode == "book-page")}, "versionNotes", true))}> diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlockHeader.jsx new file mode 100644 index 0000000000..e0bb2f933e --- /dev/null +++ b/static/js/VersionBlockHeader.jsx @@ -0,0 +1,57 @@ +import React from 'react'; +import PropTypes from "prop-types"; + +function VersionBlockHeader({text, link, onClick, renderMode}) { + return renderMode === 'versionTitle' ? + (<VersionBlockHeaderTitle + href={link} + onClick={onClick} + versionTitle={text} + />) : + (<VersionBlockHeaderText + href={link} + onClick={onClick} + versionTitle={text} + />); +} +VersionBlockHeader.prototypes = { + onClick: PropTypes.func.isRequired, + text: PropTypes.string.isRequired, + renderMode: PropTypes.string.isRequired, + link: PropTypes.string.isRequired, +}; + +function VersionBlockHeaderTitle({link, onClick, versionTitle}) { + return ( + <a + href={link} + onClick={onClick} + > + {versionTitle} + </a> + ); +} +VersionBlockHeaderTitle.prototypes = { + onClick: PropTypes.func.isRequired, + versionTitle: PropTypes.string.isRequired, + link: PropTypes.string.isRequired, +}; + +function VersionBlockHeaderText({link, onClick, text}) { + return ( + <a + className='versionPreview' + href={link} + onClick={onClick} + > + {text} + </a> + ); +} +VersionBlockHeaderText.prototypes = { + onClick: PropTypes.func.isRequired, + versionTitle: PropTypes.string.isRequired, + link: PropTypes.string.isRequired, +}; + +export default VersionBlockHeader; diff --git a/static/js/VersionBlockSelectButton.jsx b/static/js/VersionBlockSelectButton.jsx new file mode 100644 index 0000000000..b8f8c55bea --- /dev/null +++ b/static/js/VersionBlockSelectButton.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from "prop-types"; + +function VersionBlockSelectButton({link, openVersionInMoinPanel, text, isSelected}) { + return ( + <a className={`selectButton ${isSelected ? "currSelectButton" : ""}`} + href={link} + onClick={openVersionInMoinPanel} + > + {text} + </a> + ); +} +VersionBlockSelectButton.prototypes = { + openVersionInMoinPanel: PropTypes.func.isRequired, + text: PropTypes.string.isRequired, + isSelected: PropTypes.bool.isRequired, + link: PropTypes.string.isRequired, +}; +export default VersionBlockSelectButton; From 1dd9c8cbc37a43cfe8d35c027e5ca176ecfe5d40 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Tue, 21 Nov 2023 17:30:42 +0200 Subject: [PATCH 128/260] refactor(translations): new component VersionDetailsInformation extracted from VersionBlock. --- static/js/VersionBlock.jsx | 62 ++++++------------------- static/js/VersionDetailsInformation.jsx | 55 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 49 deletions(-) create mode 100644 static/js/VersionDetailsInformation.jsx diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index cf3a2a397e..06407a25ff 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -8,6 +8,7 @@ import Component from 'react-class'; import {LoadingMessage} from "./Misc"; import VersionBlockHeader from "./VersionBlockHeader"; import VersionBlockSelectButton from "./VersionBlockSelectButton"; +import VersionDetailsInformation from "./VersionDetailsInformation"; class versionTools { static makeVersionLink(currRef, version, currObjectVersions, mainPanel) { @@ -23,6 +24,12 @@ class versionTools { const versionLink = nonSelectedVersionParams === "" ? null : `/${Sefaria.normRef(currRef)}${nonSelectedVersionParams}&v${versionParam}=${version.versionTitle.replace(/\s/g,'_')}${withParam}`.replace("&","?"); return versionLink; } + static makeAttrClassNames(version, extraClassNames, attrToExist = null, attrIsMultilingual = false){ + if(attrIsMultilingual && Sefaria.interfaceLang != "english"){ + attrToExist = attrToExist+"In"+Sefaria.interfaceLang.toFirstCapital(); + } + return {...extraClassNames, "n-a": (attrToExist ? !version[attrToExist] : 0)}; + } static openVersionInSidebar(currRef, version, currObjectVersions, openVersionInSidebar, e) { e.preventDefault(); try { @@ -177,10 +184,6 @@ class VersionBlock extends Component { return null; } } - makeLicenseLink(){ - const license_map = Sefaria.getLicenseMap(); - return (this.props.version.license in license_map) ? license_map[this.props.version.license] : "#"; - } makeSelectVersionLanguage(){ let voc = this.props.version.isBaseText ? 'Version' : "Translation"; return this.props.isCurrent ? Sefaria._("Current " + voc) : Sefaria._("Select "+ voc); @@ -189,12 +192,6 @@ class VersionBlock extends Component { hasExtendedNotes(){ return !!(this.props.version.extendedNotes || this.props.version.extendedNotesHebrew); } - makeAttrClassNames(extraClassNames, attrToExist = null, attrIsMultilingual = false){ - if(attrIsMultilingual && Sefaria.interfaceLang != "english"){ - attrToExist = attrToExist+"In"+Sefaria.interfaceLang.toFirstCapital(); - } - return {...extraClassNames, "n-a": (attrToExist ? !this.props.version[attrToExist] : 0)} - } makeImageLink(){ return !!this.props.version.purchaseInformationURL ? this.props.version.purchaseInformationURL : this.props.version.versionSource; } @@ -301,7 +298,7 @@ class VersionBlock extends Component { this.props.currObjectVersions, true)} /> </div> - <div className={classNames(this.makeAttrClassNames({"versionNotes": 1, "sans-serif": (this.props.rendermode == "book-page")}, "versionNotes", true))}> + <div className={classNames(versionTools.makeAttrClassNames(v, {"versionNotes": 1, "sans-serif": (this.props.rendermode == "book-page")}, "versionNotes", true))}> <span className="" dangerouslySetInnerHTML={ {__html: vnotes} } /> <span className={`versionExtendedNotesLinks ${this.hasExtendedNotes() ? "": "n-a"}`}> <a onClick={this.openExtendedNotes} href={`/${this.props.version.title}/${this.props.version.language}/${this.props.version.versionTitle}/notes`}> @@ -311,44 +308,11 @@ class VersionBlock extends Component { </div> { !v.merged ? <div className="versionDetails sans-serif"> - <div className="versionDetailsInformation"> - <div className={classNames(this.makeAttrClassNames({"versionSource": 1, "versionDetailsElement": 1}, "versionSource"))}> - <span className="versionDetailsLabel"> - {`${Sefaria._("Source")}: `} - </span> - <a className="versionDetailsLink" href={v.versionSource} target="_blank"> - { Sefaria.util.parseUrl(v.versionSource).host.replace("www.", "") } - </a> - </div> - <div className={classNames(this.makeAttrClassNames({"versionDigitizedBySefaria": 1, "versionDetailsElement": 1}, "digitizedBySefaria"))}> - <span className="versionDetailsLabel"> - {`${Sefaria._("Digitization")}: `} - < /span> - <a className="versionDetailsLink" href="/digitized-by-sefaria" target="_blank"> - {Sefaria._("Sefaria")} - </a> - </div> - <div className={classNames(this.makeAttrClassNames({"versionLicense": 1, "versionDetailsElement": 1}, "license" ))}> - <span className="versionDetailsLabel"> - {`${Sefaria._("License")}: `} - </span> - <a className="versionDetailsLink" href={this.makeLicenseLink()} target="_blank"> - {Sefaria._(v?.license)} - </a> - </div> - <div className={classNames(this.makeAttrClassNames({"versionHistoryLink": 1, "versionDetailsElement": 1}, null))}> - <a className="versionDetailsLink" href={`/activity/${Sefaria.normRef(this.props.currentRef)}/${v.language}/${v.versionTitle && v.versionTitle.replace(/\s/g,"_")}`} target="_blank"> - {Sefaria._("Revision History")} - </a> - </div> - <div className={classNames(this.makeAttrClassNames({"versionBuyLink": 1, "versionDetailsElement": 1}, "purchaseInformationURL"))}> - <a className="versionDetailsLink" href={v.purchaseInformationURL} target="_blank"> - {Sefaria._("Buy in Print")} - </a> - </div> - </div> + <VersionDetailsInformation + currentRef={this.props.currentRef} version={this.props.version} + /> <div className="versionDetailsImage"> - <div className={classNames(this.makeAttrClassNames({"versionBuyImage": 1, "versionDetailsElement": 1} , "purchaseInformationImage"))}> + <div className={classNames(versionTools.makeAttrClassNames(v, {"versionBuyImage": 1, "versionDetailsElement": 1} , "purchaseInformationImage"))}> <a className="versionDetailsLink versionDetailsImageLink" href={this.makeImageLink()} target="_blank"> <img className="versionImage" src={this.makeImageSrc()} alt={Sefaria._("Buy Now")} /> </a> @@ -494,4 +458,4 @@ VersionsBlocksList.defaultProps = { -export {VersionBlock as default, VersionsBlocksList}; +export {VersionBlock as default, VersionsBlocksList, versionTools}; diff --git a/static/js/VersionDetailsInformation.jsx b/static/js/VersionDetailsInformation.jsx new file mode 100644 index 0000000000..69db372bcb --- /dev/null +++ b/static/js/VersionDetailsInformation.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from "classnames"; +import Sefaria from "./sefaria/sefaria"; +import {versionTools} from './VersionBlock'; + +function VersionDetailsInformation({currentRef, version}) { + function makeLicenseLink() { + const license_map = Sefaria.getLicenseMap(); + return (version.license in license_map) ? license_map[version.license] : "#"; + } + return ( + <div className="versionDetailsInformation"> + <div className={classNames(versionTools.makeAttrClassNames(version, {"versionSource": 1, "versionDetailsElement": 1}, "versionSource"))}> + <span className="versionDetailsLabel"> + {`${Sefaria._("Source")}: `} + </span> + <a className="versionDetailsLink" href={version.versionSource} target="_blank"> + { Sefaria.util.parseUrl(version.versionSource).host.replace("www.", "") } + </a> + </div> + <div className={classNames(versionTools.makeAttrClassNames(version, {"versionDigitizedBySefaria": 1, "versionDetailsElement": 1}, "digitizedBySefaria"))}> + <span className="versionDetailsLabel"> + {`${Sefaria._("Digitization")}: `} + < /span> + <a className="versionDetailsLink" href="/digitized-by-sefaria" target="_blank"> + {Sefaria._("Sefaria")} + </a> + </div> + <div className={classNames(versionTools.makeAttrClassNames(version, {"versionLicense": 1, "versionDetailsElement": 1}, "license" ))}> + <span className="versionDetailsLabel"> + {`${Sefaria._("License")}: `} + </span> + <a className="versionDetailsLink" href={makeLicenseLink()} target="_blank"> + {Sefaria._(version?.license)} + </a> + </div> + <div className={classNames(versionTools.makeAttrClassNames(version, {"versionHistoryLink": 1, "versionDetailsElement": 1}, null))}> + <a className="versionDetailsLink" href={`/activity/${Sefaria.normRef(currentRef)}/${version.language}/${version.versionTitle && version.versionTitle.replace(/\s/g,"_")}`} target="_blank"> + {Sefaria._("Revision History")} + </a> + </div> + <div className={classNames(versionTools.makeAttrClassNames(version, {"versionBuyLink": 1, "versionDetailsElement": 1}, "purchaseInformationURL"))}> + <a className="versionDetailsLink" href={version.purchaseInformationURL} target="_blank"> + {Sefaria._("Buy in Print")} + </a> + </div> + </div> + ); +} +VersionDetailsInformation.prototypes = { + currentRef: PropTypes.string.isRequired, + version: PropTypes.object.isRequired, +} +export default VersionDetailsInformation; From 5678f8fb2e9a668dca59f08f80fd5533282bcf40 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 22 Nov 2023 09:01:02 +0200 Subject: [PATCH 129/260] refactor(translation): new component VersionDetailsImage extracted from VersionBlock. --- static/js/VersionBlock.jsx | 19 +++-------------- static/js/VersionDetailsImage.jsx | 27 +++++++++++++++++++++++++ static/js/VersionDetailsInformation.jsx | 2 +- 3 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 static/js/VersionDetailsImage.jsx diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index 06407a25ff..bd1a1bd9c7 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -9,6 +9,7 @@ import {LoadingMessage} from "./Misc"; import VersionBlockHeader from "./VersionBlockHeader"; import VersionBlockSelectButton from "./VersionBlockSelectButton"; import VersionDetailsInformation from "./VersionDetailsInformation"; +import VersionDetailsImage from "./VersionDetailsImage"; class versionTools { static makeVersionLink(currRef, version, currObjectVersions, mainPanel) { @@ -192,12 +193,6 @@ class VersionBlock extends Component { hasExtendedNotes(){ return !!(this.props.version.extendedNotes || this.props.version.extendedNotesHebrew); } - makeImageLink(){ - return !!this.props.version.purchaseInformationURL ? this.props.version.purchaseInformationURL : this.props.version.versionSource; - } - makeImageSrc(){ - return !!this.props.version.purchaseInformationImage ? this.props.version.purchaseInformationImage : "data:,"; - } render() { if(this.props.version.title == "Sheet") return null //why are we even getting here in such a case??; @@ -308,16 +303,8 @@ class VersionBlock extends Component { </div> { !v.merged ? <div className="versionDetails sans-serif"> - <VersionDetailsInformation - currentRef={this.props.currentRef} version={this.props.version} - /> - <div className="versionDetailsImage"> - <div className={classNames(versionTools.makeAttrClassNames(v, {"versionBuyImage": 1, "versionDetailsElement": 1} , "purchaseInformationImage"))}> - <a className="versionDetailsLink versionDetailsImageLink" href={this.makeImageLink()} target="_blank"> - <img className="versionImage" src={this.makeImageSrc()} alt={Sefaria._("Buy Now")} /> - </a> - </div> - </div> + <VersionDetailsInformation currentRef={this.props.currentRef} version={v}/> + <VersionDetailsImage version={v}/> </div> : null } </div> diff --git a/static/js/VersionDetailsImage.jsx b/static/js/VersionDetailsImage.jsx new file mode 100644 index 0000000000..d68b98e801 --- /dev/null +++ b/static/js/VersionDetailsImage.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from "classnames"; +import Sefaria from "./sefaria/sefaria"; +import {versionTools} from "./VersionBlock"; + +function VersionDetailsImage({version}) { + function makeImageLink() { + return !!version.purchaseInformationURL ? version.purchaseInformationURL : version.versionSource; + } + function makeImageSrc(){ + return !!version.purchaseInformationImage ? version.purchaseInformationImage : "data:,"; + } + return ( + <div className="versionDetailsImage"> + <div className={classNames(versionTools.makeAttrClassNames(version, {"versionBuyImage": 1, "versionDetailsElement": 1} , "purchaseInformationImage"))}> + <a className="versionDetailsLink versionDetailsImageLink" href={makeImageLink()} target="_blank"> + <img className="versionImage" src={makeImageSrc()} alt={Sefaria._("Buy Now")} /> + </a> + </div> + </div> + ) +} +VersionDetailsImage.prototypes = { + version: PropTypes.object.isRequired, +}; +export default VersionDetailsImage; diff --git a/static/js/VersionDetailsInformation.jsx b/static/js/VersionDetailsInformation.jsx index 69db372bcb..b2cba54de9 100644 --- a/static/js/VersionDetailsInformation.jsx +++ b/static/js/VersionDetailsInformation.jsx @@ -51,5 +51,5 @@ function VersionDetailsInformation({currentRef, version}) { VersionDetailsInformation.prototypes = { currentRef: PropTypes.string.isRequired, version: PropTypes.object.isRequired, -} +}; export default VersionDetailsInformation; From b8ada142360c37aee413d65395c7170dfa8e3e6f Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 22 Nov 2023 09:18:17 +0200 Subject: [PATCH 130/260] refactor(translations): move makeVersionTitle to versionTools. --- static/js/VersionBlock.jsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index bd1a1bd9c7..0ad91e1da5 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -12,6 +12,15 @@ import VersionDetailsInformation from "./VersionDetailsInformation"; import VersionDetailsImage from "./VersionDetailsImage"; class versionTools { + static makeVersionTitle(version){ + if (version.merged) { + return {"className" : "", "text": Sefaria._("Merged from") + " " + Array.from(new Set(version.sources)).join(", ")}; + } else if (Sefaria.interfaceLang === "english" || !version.versionTitleInHebrew) { + return {"className" : "", "text" : version.versionTitle}; + } else { + return {"className": "he", "text": version.versionTitleInHebrew}; + } + } static makeVersionLink(currRef, version, currObjectVersions, mainPanel) { if (version.merged) { return "#"; // there's no url for a merged version @@ -164,15 +173,6 @@ class VersionBlock extends Component { e.preventDefault(); this.props.viewExtendedNotes(this.props.version.title, this.props.version.language, this.props.version.versionTitle); } - makeVersionTitle(){ - if(this.props.version.merged){ - return {"className": "", "text": Sefaria._("Merged from") + " " + Array.from(new Set(this.props.version.sources)).join(", ")}; - }else if(Sefaria.interfaceLang=="english" || !this.props.version.versionTitleInHebrew){ - return {"className":"", "text":this.props.version.versionTitle}; - }else{ - return {"className": "he", "text": this.props.version.versionTitleInHebrew}; - } - } makeVersionNotes(){ if (!this.props.showNotes) { return null; @@ -197,7 +197,7 @@ class VersionBlock extends Component { render() { if(this.props.version.title == "Sheet") return null //why are we even getting here in such a case??; const v = this.props.version; - const vtitle = this.makeVersionTitle(); + const vtitle = versionTools.makeVersionTitle(v); const vnotes = this.makeVersionNotes(); const showLanguagLabel = this.props.rendermode == "book-page"; const openVersionInSidebar = versionTools.openVersionInSidebar.bind(null, this.props.currentRef, this.props.version, From 1f53377851b3dac62dd2c33b8b3cb1f14568b49d Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 22 Nov 2023 11:07:38 +0200 Subject: [PATCH 131/260] refactor(translations): add option for different text in OpenConnectionTabButton. --- static/js/TextList.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/static/js/TextList.jsx b/static/js/TextList.jsx index 510d1eb44e..92c42638dc 100644 --- a/static/js/TextList.jsx +++ b/static/js/TextList.jsx @@ -312,12 +312,13 @@ const DeleteConnectionButton = ({delUrl, connectionDeleteCallback}) =>{ } -const OpenConnectionTabButton = ({srefs, openInTabCallback}) =>{ +const OpenConnectionTabButton = ({srefs, openInTabCallback, renderMode}) =>{ /* ConnectionButton composite element. Goes inside a ConnectionButtons Takes a ref(s) for opening as a link and callback for opening in-app */ const sref = Array.isArray(srefs) ? Sefaria.normRefList(srefs) : srefs; + const [en, he] = renderMode === 'versionPreview' ? ['Open Text', 'פתיחת טקסט'] : ['Open', 'פתיחה']; const openLinkInTab = (event) => { if (openInTabCallback) { event.preventDefault(); @@ -330,8 +331,8 @@ const OpenConnectionTabButton = ({srefs, openInTabCallback}) =>{ <SimpleLinkedBlock aclasses={"connection-button panel-open-link"} onClick={openLinkInTab} - en={"Open"} - he={"פתיחה"} + en={en} + he={he} url={`/${sref}`} /> ); From 1bf6cf89906cf77abfdbf077df8a478e82d7f06d Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Wed, 22 Nov 2023 13:15:52 +0200 Subject: [PATCH 132/260] feat(Topic Editor): can add pictures with captions --- reader/views.py | 25 ++++++++----------------- sefaria/helper/topic.py | 3 +++ static/js/AdminEditor.jsx | 13 ++++--------- static/js/Misc.jsx | 37 +++++++++++++++++-------------------- static/js/TopicEditor.jsx | 22 +++++++++++++++++----- 5 files changed, 49 insertions(+), 51 deletions(-) diff --git a/reader/views.py b/reader/views.py index 951667996d..086eed3a7a 100644 --- a/reader/views.py +++ b/reader/views.py @@ -3104,7 +3104,6 @@ def add_new_topic_api(request): isTopLevelDisplay = data["category"] == Topic.ROOT t = Topic({'slug': "", "isTopLevelDisplay": isTopLevelDisplay, "data_source": "sefaria", "numSources": 0}) update_topic_titles(t, data) - if not isTopLevelDisplay: # not Top Level so create an IntraTopicLink to category new_link = IntraTopicLink({"toTopic": data["category"], "fromTopic": t.slug, "linkType": "displays-under", "dataSource": "sefaria"}) new_link.save() @@ -3116,8 +3115,11 @@ def add_new_topic_api(request): t.data_source = "sefaria" # any topic edited manually should display automatically in the TOC and this flag ensures this if "description" in data: t.change_description(data["description"], data.get("categoryDescription", None)) - t.save() + if "image" in data: + t.image = data["image"] + + t.save() library.build_topic_auto_completer() library.get_topic_toc(rebuild=True) library.get_topic_toc_json(rebuild=True) @@ -3570,28 +3572,17 @@ def profile_follow_api(request, ftype, slug): @catch_error_as_json def topic_upload_photo(request): if not request.user.is_authenticated: - return jsonResponse({"error": _("You must be logged in to update your profile photo.")}) + return jsonResponse({"error": _("You must be logged in to update a topic photo.")}) if request.method == "POST": from io import BytesIO import uuid import base64 - """ - "image" : { - "image_uri" : "https://storage.googleapis.com/img.sefaria.org/topics/shabbat.jpg", - "image_caption" : { - "en" : "Friday Evening, Isidor Kaufmann, Austria c. 1920. The Jewish Museum, Gift of Mr. and Mrs. M. R. Schweitzer", - "he" : "שישי בערב, איזידור קאופמן, וינה 1920. המוזיאון היהודי בניו יורק, מתנת מר וגברת מ.ר. שוויצר" - } - } -Validation that the image_uri url should start with https://storage.googleapis.com/img.sefaria.org/topics/ - """ bucket_name = GoogleStorageManager.TOPICS_BUCKET img_file_in_mem = BytesIO(base64.b64decode(request.POST.get('file'))) + old_filename = request.POST.get('old_filename') img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"topics/{request.user.id}-{uuid.uuid1()}.gif", - bucket_name) - #big_pic_url = GoogleStorageManager.upload_file(get_resized_file(image, (250, 250)), "{}-{}.png".format(profile.slug, now), bucket_name, old_big_pic_filename) - - add_image_to_topic(topic_slug, img_url, en_caption, he_caption) + bucket_name, old_filename=old_filename) + #img_url = 'https://storage.googleapis.com/img.sefaria.org/topics/41861-683e06f6-891a-11ee-be47-4a26184f1ad1.gif' return jsonResponse({"url": img_url}) return jsonResponse({"error": "Unsupported HTTP method."}) diff --git a/sefaria/helper/topic.py b/sefaria/helper/topic.py index 504fe11ade..68ce1e0e07 100644 --- a/sefaria/helper/topic.py +++ b/sefaria/helper/topic.py @@ -1107,6 +1107,9 @@ def update_topic(topic_obj, **kwargs): if "description" in kwargs or "categoryDescription" in kwargs: topic_obj.change_description(kwargs.get("description", None), kwargs.get("categoryDescription", None)) + if "image" in kwargs: + topic_obj.image = kwargs["image"] + topic_obj.save() if kwargs.get('rebuild_topic_toc', True): diff --git a/static/js/AdminEditor.jsx b/static/js/AdminEditor.jsx index 0f32c72d1f..566973dacd 100644 --- a/static/js/AdminEditor.jsx +++ b/static/js/AdminEditor.jsx @@ -1,10 +1,12 @@ import React, {useRef, useState} from "react"; import Sefaria from "./sefaria/sefaria"; -import {AdminToolHeader, InterfaceText, PictureUploader, TitleVariants} from "./Misc"; +import {AdminToolHeader, InterfaceText, TitleVariants} from "./Misc"; import sanitizeHtml from 'sanitize-html'; import classNames from "classnames"; const options_for_form = { - "Picture": {label: "Picture", field: "picture", placeholder: "Add a picture.", type: "picture"}, + // "Picture": {label: "Picture", field: "picture", placeholder: "Add a picture.", type: "picture"}, + "English Caption": {label: "English Caption", field: "enImgCaption", placeholder: "Add a caption for topic picture"}, + "Hebrew Caption": {label: "Hebrew Caption", field: "heImgCaption", placeholder: "Add a Hebrew caption for topic picture"}, "Title": {label: "Title", field: "enTitle", placeholder: "Add a title."}, "Hebrew Title": {label: "Hebrew Title", field: "heTitle", placeholder: "Add a title."}, "English Description": { @@ -126,10 +128,6 @@ const AdminEditor = ({title, data, close, catMenu, updateData, savingStatus, } updateData({...data}); } - const handlePictureChange = (url) => { - data["picture"] = url; - updateData({...data}); - } const handleTitleVariants = (newTitles, field) => { const newData = {...data}; newData[field] = newTitles.map(x => Object.assign({}, x)); @@ -166,9 +164,6 @@ const AdminEditor = ({title, data, close, catMenu, updateData, savingStatus, const item = ({label, field, placeholder, type, dropdown_data}) => { let obj; switch(type) { - case 'picture': - obj = <PictureUploader callback={handlePictureChange}/>; - break; case 'dropdown': obj = getDropdown(field, dropdown_data, placeholder); break; diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index e0a820257b..2988f55fa7 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -15,7 +15,7 @@ import {ContentText} from "./ContentText"; import ReactTags from "react-tag-autocomplete"; import {AdminEditorButton, useEditToggle} from "./AdminEditor"; import {CategoryEditor, ReorderEditor} from "./CategoryEditor"; -import {refSort} from "./TopicPage"; +import {refSort, TopicImage} from "./TopicPage"; import {TopicEditor} from "./TopicEditor"; import {generateContentForModal, SignUpModalKind} from './sefaria/signupModalContent'; import {SourceEditor} from "./SourceEditor"; @@ -1235,7 +1235,8 @@ const EditorForExistingTopic = ({ toggle, data }) => { origBirthYear: data?.properties?.birthYear?.value, origDeathPlace: data?.properties?.deathPlace?.value, origDeathYear: data?.properties?.deathYear?.value, - origEra: data?.properties?.era?.value + origEra: data?.properties?.era?.value, + origImage: data?.image, }; @@ -1578,14 +1579,18 @@ FollowButton.propTypes = { }; -const PictureUploader = (callback) => { +const PictureUploader = ({callback, old_filename, caption}) => { + /* + `old_filename` is passed to API so that if it exists, it is deleted + */ const fileInput = useRef(null); var uploadImage = function(imageData) { const formData = new FormData(); formData.append('file', imageData.replace(/data:image\/(jpe?g|png|gif);base64,/, "")); - // formData.append('file', imageData); - + if (old_filename !== "") { + formData.append('old_filename', old_filename); + } $.ajax({ url: Sefaria.apiHost + "/api/topics/upload-image", type: 'POST', @@ -1594,9 +1599,6 @@ const PictureUploader = (callback) => { processData: false, success: function(data) { callback(data.url); - // $("#inlineAddMediaInput").val(data.url); - // $("#addmediaDiv").find(".button").first().trigger("click"); - // $("#inlineAddMediaInput").val(""); }, error: function(e) { console.log("photo upload ERROR", e); @@ -1623,18 +1625,13 @@ const PictureUploader = (callback) => { alert('not an image'); } } - return <div><div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton"> - <label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"></label> - </div><input id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} /> - <div className="section"> - <label><InterfaceText>English Caption</InterfaceText></label> - <input type="text" id="enCaption"/> - </div> - <div className="section"> - <label><InterfaceText>Hebrew Caption</InterfaceText></label> - <input type="text" id="heCaption"/> - </div> - </div> + return <div className="section"> + <label><InterfaceText>Picture</InterfaceText></label> + <div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton"> + <label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"></label> + </div><input id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} /> + {old_filename !== "" && <div style={{"max-width": "420px"}}><br/><ImageWithCaption photoLink={old_filename} caption={caption}/></div>} + </div> } const CategoryColorLine = ({category}) => diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 229ba62da2..963f0fcac3 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -1,5 +1,5 @@ import Sefaria from "./sefaria/sefaria"; -import {InterfaceText, requestWithCallBack, ProfilePic} from "./Misc"; +import {InterfaceText, requestWithCallBack, ProfilePic, PictureUploader} from "./Misc"; import $ from "./sefaria/sefariaJquery"; import {AdminEditor} from "./AdminEditor"; import {Reorder} from "./CategoryEditor"; @@ -19,7 +19,9 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { birthYear: origData.origBirthYear || "", heDeathPlace: origData.origHeDeathPlace || "", deathYear: origData.origDeathYear || "", era: origData.origEra || "", deathPlace: origData.origDeathPlace || "", - picture: origData?.origPicture || "" + enImgCaption: origData?.origImage?.image_caption?.en || "", + heImgCaption: origData?.origImage?.image_caption?.he || "", + image_uri: origData?.origImage?.image_uri || "" }); const isNew = !('origSlug' in origData); const [savingStatus, setSavingStatus] = useState(false); @@ -68,7 +70,6 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { const updateData = function(newData) { setIsChanged(true); setData(newData); - console.log(newData); } const validate = async function () { if (!isChanged) { @@ -100,6 +101,9 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { postData.altTitles.en = data.enAltTitles.map(x => x.name); // alt titles implemented using TitleVariants which contains list of objects with 'name' property. postData.altTitles.he = data.heAltTitles.map(x => x.name); + if (data.image_uri !== "") { + postData.image = {"image_uri": data.image_uri, "image_caption": {"en": data.enImgCaption, "he": data.heImgCaption}} + } // add descriptions if they changed const origDescription = {en: origData?.origEnDescription || "", he: origData?.origHeDescription || ""}; const origCategoryDescription = {en: origData?.origEnCategoryDescription || "", he: origData?.origHeCategoryDescription || ""}; @@ -155,12 +159,16 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { alert("Unfortunately, there may have been an error saving this topic information: " + errorThrown.toString()); }); } + const handlePictureChange = (url) => { + data["image_uri"] = url; + updateData({...data}); + } const deleteObj = function() { const url = `/api/topic/delete/${data.origSlug}`; requestWithCallBack({url, type: "DELETE", redirect: () => window.location.href = "/topics"}); } - let items = ["Title", "Hebrew Title", "English Description", "Hebrew Description", "Category Menu", "Picture"]; + let items = ["Title", "Hebrew Title", "English Description", "Hebrew Description", "Category Menu"]; if (isCategory) { items.push("English Short Description"); items.push("Hebrew Short Description"); @@ -169,10 +177,14 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { const authorItems = ["English Alternate Titles", "Hebrew Alternate Titles", "Birth Place", "Hebrew Birth Place", "Birth Year", "Place of Death", "Hebrew Place of Death", "Death Year", "Era"]; authorItems.forEach(x => items.push(x)); } + items.push("English Caption"); + items.push("Hebrew Caption"); return <AdminEditor title="Topic Editor" close={close} catMenu={catMenu} data={data} savingStatus={savingStatus} validate={validate} deleteObj={deleteObj} updateData={updateData} isNew={isNew} items={items} extras={ - [isNew ? null : + [<PictureUploader callback={handlePictureChange} old_filename={data.image_uri} + caption={{en: data.enImgCaption, he: data.heImgCaption}}/>, + isNew ? null : <Reorder subcategoriesAndBooks={sortedSubtopics} updateOrder={setSortedSubtopics} displayType="topics"/>, From ab984852d63f8c569ce81e80ef8c8502374a05b3 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Wed, 22 Nov 2023 13:50:13 +0200 Subject: [PATCH 133/260] fix(Topic Editor): when uploading topic photo, delete old one --- reader/views.py | 2 ++ sefaria/google_storage_manager.py | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/reader/views.py b/reader/views.py index 086eed3a7a..41320d133b 100644 --- a/reader/views.py +++ b/reader/views.py @@ -3580,6 +3580,8 @@ def topic_upload_photo(request): bucket_name = GoogleStorageManager.TOPICS_BUCKET img_file_in_mem = BytesIO(base64.b64decode(request.POST.get('file'))) old_filename = request.POST.get('old_filename') + if old_filename: + old_filename = f"topics/{old_filename.split('/')[-1]}" img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"topics/{request.user.id}-{uuid.uuid1()}.gif", bucket_name, old_filename=old_filename) #img_url = 'https://storage.googleapis.com/img.sefaria.org/topics/41861-683e06f6-891a-11ee-be47-4a26184f1ad1.gif' diff --git a/sefaria/google_storage_manager.py b/sefaria/google_storage_manager.py index 4bd2fa6c23..756dbc60e4 100644 --- a/sefaria/google_storage_manager.py +++ b/sefaria/google_storage_manager.py @@ -25,9 +25,8 @@ class GoogleStorageManager(object): @classmethod def get_bucket(cls, bucket_name): if getattr(cls, 'client', None) is None: - # for local development, change below line to - cls.client = storage.Client(project="production-deployment") - #cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) + # for local development, change below line to cls.client = storage.Client(project="production-deployment") + cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) bucket = cls.client.get_bucket(bucket_name) return bucket From 939d7dea02fbba97855130b5d1ec6fa9c0ee453c Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 22 Nov 2023 15:29:25 +0200 Subject: [PATCH 134/260] feat(translations): functions for getting texts and particularly translations. Use the new function in TranslationBox. --- static/js/TranslationsBox.jsx | 4 ++-- static/js/sefaria/sefaria.js | 29 ++++++++++++++++++++++++++++ static/js/sefaria/textGetter.js | 34 --------------------------------- 3 files changed, 31 insertions(+), 36 deletions(-) delete mode 100644 static/js/sefaria/textGetter.js diff --git a/static/js/TranslationsBox.jsx b/static/js/TranslationsBox.jsx index 9e0c84382b..f6d0410d9f 100644 --- a/static/js/TranslationsBox.jsx +++ b/static/js/TranslationsBox.jsx @@ -19,12 +19,12 @@ class TranslationsBox extends Component { } componentDidMount() { if(!this.isSheet()) { - Sefaria.getTranslations(this.props.sectionRef).then(this.onVersionsLoad); + Sefaria.getAllTranslationsWithText(this.props.sectionRef).then(this.onVersionsLoad); } } componentDidUpdate(prevProps, prevState) { if (!this.isSheet() && prevProps.sectionRef !== this.props.sectionRef) { - Sefaria.getTranslations(this.props.sectionRef).then(this.onVersionsLoad); + Sefaria.getAllTranslationsWithText(this.props.sectionRef).then(this.onVersionsLoad); } } onVersionsLoad(versions) { diff --git a/static/js/sefaria/sefaria.js b/static/js/sefaria/sefaria.js index 3da5849570..558b4dd938 100644 --- a/static/js/sefaria/sefaria.js +++ b/static/js/sefaria/sefaria.js @@ -487,6 +487,35 @@ Sefaria = extend(Sefaria, { return Promise.all(promises).then(results => Object.assign({}, ...results)); }, + getTextsFromAPIV3: async function(ref, requiredVersions, mergeText) { + // ref is segment ref or bottom level section ref + // requiredVersions is array of objects that can have language and versionTitle + function makeParamsString(language, versionTitle) { + if (versionTitle) { + return `${language}|${versionTitle}`; + } else if (language) { + return language; + } + } + function makeUrl() { + const host = Sefaria.apiHost; + const endPoint = '/api/v3/texts/' + const versions = requiredVersions.map(obj => + makeParamsString(obj.language, obj.versionTitle) + ); + const url = `${host}${endPoint}${ref}?version=${versions.join('&version=')}&fill_in_missing_segments=${mergeText}`; + return url; + } + const url = makeUrl(ref, requiredVersions); + //here's the place for getting it from cache + const apiObject = await Sefaria._ApiPromise(url); + //here's the place for all changes we want to add, and saving in cache + return apiObject; + }, + getAllTranslationsWithText: async function(ref) { + let returnObj = await Sefaria.getTextsFromAPIV3(ref, [{language: 'translation', versionTitle: 'all'}], false); + return Sefaria._sortVersionsIntoBuckets(returnObj.versions); + }, _bulkSheets: {}, getBulkSheets: function(sheetIds) { if (sheetIds.length === 0) { return Promise.resolve({}); } diff --git a/static/js/sefaria/textGetter.js b/static/js/sefaria/textGetter.js deleted file mode 100644 index 572fe3cc48..0000000000 --- a/static/js/sefaria/textGetter.js +++ /dev/null @@ -1,34 +0,0 @@ -import Sefaria from "./sefaria"; - -function makeParamsString(language, versionTitle) { - if (versionTitle) { - return `${language}|${versionTitle}`; - } else if (language) { - return language; - } -} - -function makeUrl(ref, requiredVersions) { - const host = Sefaria.apiHost; - const endPoint = '/api/v3/texts/' - const versions = Object.entries(requiredVersions).map(([language, versionTitle]) => - makeParamsString(language, versionTitle) - ); - const url = `${host}${endPoint}${ref}?version=${versions.join('&version=')}&fill_in_missing_segments=true`; - return url; -} - -async function getVTextsFromAPI(ref, requiredVersions) { - const url = makeUrl(ref, requiredVersions); - const apiObject = await Sefaria._ApiPromise(url); - Sefaria.saveVersions(ref, apiObject.available_versions); - delete apiObject.available_versions; - return apiObject; -} - -export async function getTexts(ref, requiredVersions) { - // ref is segment ref or bottom level section ref - // requiredVersions is array of objects that can have language and versionTitle - let returnObj = await getVersionsFromAPI(ref, requiredVersions); - return returnObj; -} From c82d71180840f992912f9dcd6ce54edddd87b980 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 22 Nov 2023 15:39:33 +0200 Subject: [PATCH 135/260] refactor(text api): change fullLanguage to languageFamilyName. --- sefaria/model/text.py | 12 ++++++------ sefaria/model/text_manager.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 50ce59e93b..f0e460b34d 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1335,7 +1335,7 @@ class Version(AbstractTextRecord, abst.AbstractMongoRecord, AbstractSchemaConten "purchaseInformationURL", "hasManuallyWrappedRefs", # true for texts where refs were manually wrapped in a-tags. no need to run linker at run-time. "actualLanguage", - 'fullLanguage', + 'languageFamilyName', "isBaseText", 'isSource', 'isPrimary', @@ -1357,11 +1357,11 @@ def _validate(self): languageCodeRe = re.search(r"\[([a-z]{2})\]$", getattr(self, "versionTitle", None)) if languageCodeRe and languageCodeRe.group(1) != getattr(self,"actualLanguage",None): self.actualLanguage = languageCodeRe.group(1) - if not getattr(self, 'fullLanguage', None): + if not getattr(self, 'languageFamilyName', None): try: - self.fullLanguage = constants.LANGUAGE_CODES[self.actualLanguage] + self.languageFamilyName = constants.LANGUAGE_CODES[self.actualLanguage] except KeyError: - self.fullLanguage = constants.LANGUAGE_CODES[self.language] + self.languageFamilyName = constants.LANGUAGE_CODES[self.language] if getattr(self,"language", None) not in ["en", "he"]: raise InputError("Version language must be either 'en' or 'he'") index = self.get_index() @@ -1715,7 +1715,7 @@ def __init__(self, oref, lang, vtitle, merge_versions=False): def versions(self): if self._versions == []: condition_query = self.oref.condition_query(self.lang) if self.merge_versions else \ - {'title': self.oref.index.title, 'fullLanguage': self.lang, 'versionTitle': self.vtitle} + {'title': self.oref.index.title, 'languageFamilyName': self.lang, 'versionTitle': self.vtitle} self._versions = VersionSet(condition_query, proj=self.oref.part_projection()) return self._versions @@ -1728,7 +1728,7 @@ def _validate_versions(self, versions): if not self.merge_versions and len(versions) > 1: raise InputError("Got many versions instead of one") for version in versions: - condition = version.title == self.oref.index.title and version.fullLanguage == self.lang + condition = version.title == self.oref.index.title and version.languageFamilyName == self.lang if not self.merge_versions: condition = condition and version.versionTitle == self.vtitle if not condition: diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 7b9a3bba06..2d8b3087a3 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -28,7 +28,7 @@ def __init__(self, oref: Ref, versions_params: List[List[str]], fill_in_missing_ self.return_obj = { 'versions': [], 'missings': [], - 'available_langs': sorted({v.fullLanguage for v in self.all_versions}), + 'available_langs': sorted({v.languageFamilyName for v in self.all_versions}), 'available_versions': [{f: getattr(v, f, "") for f in fields} for v in self.all_versions] } @@ -38,12 +38,12 @@ def _append_version(self, version): for attr in ['chapter', 'title', 'language']: fields.remove(attr) version_details = {f: getattr(version, f, "") for f in fields} - text_range = TextRange(self.oref, version.fullLanguage, version.versionTitle, self.fill_in_missing_segments) + text_range = TextRange(self.oref, version.languageFamilyName, version.versionTitle, self.fill_in_missing_segments) if self.fill_in_missing_segments: # we need a new VersionSet of only the relevant versions for merging. copy should be better than calling for mongo relevant_versions = copy.copy(self.all_versions) - relevant_versions.remove(lambda v: v.fullLanguage != version.fullLanguage) + relevant_versions.remove(lambda v: v.languageFamilyName != version.languageFamilyName) else: relevant_versions = [version] text_range.versions = relevant_versions @@ -66,7 +66,7 @@ def _append_required_versions(self, lang: str, vtitle: str) -> None: elif lang == self.TRANSLATION: lang_condition = lambda v: not getattr(v, 'isSource', False) elif lang: - lang_condition = lambda v: v.fullLanguage == lang + lang_condition = lambda v: v.languageFamilyName == lang else: lang_condition = lambda v: True if vtitle and vtitle != self.ALL: @@ -76,7 +76,7 @@ def _append_required_versions(self, lang: str, vtitle: str) -> None: if vtitle != self.ALL and versions: versions = [max(versions, key=lambda v: getattr(v, 'priority', 0))] for version in versions: - if all(version.fullLanguage != v['fullLanguage'] or version.versionTitle != v['versionTitle'] for v in self.return_obj['versions']): + if all(version.languageFamilyName != v['languageFamilyName'] or version.versionTitle != v['versionTitle'] for v in self.return_obj['versions']): #do not return the same version even if included in two different version params self._append_version(version) if not versions: From 336e758b4610c67ee60531419a40e097f2c008f3 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 22 Nov 2023 15:50:17 +0200 Subject: [PATCH 136/260] feat(text api): return the language attribute to api response. --- sefaria/model/text_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 2d8b3087a3..c39f6c79eb 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -35,7 +35,7 @@ def __init__(self, oref: Ref, versions_params: List[List[str]], fill_in_missing_ def _append_version(self, version): #TODO part of this function duplicate the functionality of Ref.versionlist(). maybe we should mvoe it to Version fields = Version.optional_attrs + Version.required_attrs - for attr in ['chapter', 'title', 'language']: + for attr in ['chapter', 'title']: fields.remove(attr) version_details = {f: getattr(version, f, "") for f in fields} text_range = TextRange(self.oref, version.languageFamilyName, version.versionTitle, self.fill_in_missing_segments) @@ -175,7 +175,7 @@ def make_named_entities_dict(): for version in self.return_obj['versions']: if self.return_format == 'wrap_all_entities': - language = 'he' if version['direction'] == 'rtl' else 'en' + language = 'he' if version['direction'] == 'rtl' else 'en' #this is neccesary because we want to get rif of the language attribute in future ne_by_secs = make_named_entities_dict() ja = JaggedTextArray(version['text']) # JaggedTextArray works also with depth 0, i.e. a string From 18dad10d8e8fbd07c45f6b2df3aa0b5e98415749 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 22 Nov 2023 15:58:13 +0200 Subject: [PATCH 137/260] feat(text api): find language also with capital letters. --- sefaria/model/text_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index c39f6c79eb..5a223d8c2c 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -66,7 +66,7 @@ def _append_required_versions(self, lang: str, vtitle: str) -> None: elif lang == self.TRANSLATION: lang_condition = lambda v: not getattr(v, 'isSource', False) elif lang: - lang_condition = lambda v: v.languageFamilyName == lang + lang_condition = lambda v: v.languageFamilyName.lower() == lang else: lang_condition = lambda v: True if vtitle and vtitle != self.ALL: From db17da1a8d6be98e9ea2ed0b62feb5597f1609ff Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 22 Nov 2023 16:03:40 +0200 Subject: [PATCH 138/260] refactor(text api): replace base by primary. --- api/tests.py | 8 ++++---- api/views.py | 2 +- sefaria/model/text_manager.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/tests.py b/api/tests.py index 1ea31d542b..76709e1612 100644 --- a/api/tests.py +++ b/api/tests.py @@ -83,14 +83,14 @@ def test_api_get_text_specific(self): self.assertEqual(data["sections"], ["2a", '4', '1']) self.assertEqual(data["toSections"], ["2a", '4', '1']) - def test_api_get_text_base_all(self): - response = c.get('/api/v3/texts/Genesis.1?version=base|all') + def test_api_get_text_primary_all(self): + response = c.get('/api/v3/texts/Genesis.1?version=primary|all') data = json.loads(response.content) self.assertTrue(len(data["versions"]) > 3) self.assertTrue(all(v['actualLanguage'] == 'he' for v in data["versions"])) - def test_api_get_text_base(self): - response = c.get('/api/v3/texts/Shabbat.22a?version=base') + def test_api_get_text_primary(self): + response = c.get('/api/v3/texts/Shabbat.22a?version=primary') self.assertEqual(200, response.status_code) data = json.loads(response.content) self.assertEqual(len(data["versions"]), 1) diff --git a/api/views.py b/api/views.py index c6e0beca82..982818902c 100644 --- a/api/views.py +++ b/api/views.py @@ -46,7 +46,7 @@ def get(self, request, *args, **kwargs): return jsonResponse({'error': f'We have no text for {self.oref}.'}, status=400) versions_params = request.GET.getlist('version', []) if not versions_params: - versions_params = ['base'] + versions_params = ['primary'] versions_params = [self.split_piped_params(param_str) for param_str in versions_params] fill_in_missing_segments = request.GET.get('fill_in_missing_segments', False) return_format = request.GET.get('return_format', 'default') diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_manager.py index 5a223d8c2c..f14a54bac8 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_manager.py @@ -11,7 +11,7 @@ class TextManager: ALL = 'all' - BASE = 'base' + PRIMARY = 'primary' SOURCE = 'source' TRANSLATION = 'translation' @@ -59,7 +59,7 @@ def _append_version(self, version): self.return_obj['versions'].append(version_details) def _append_required_versions(self, lang: str, vtitle: str) -> None: - if lang == self.BASE: + if lang == self.PRIMARY: lang_condition = lambda v: getattr(v, 'isPrimary', False) elif lang == self.SOURCE: lang_condition = lambda v: getattr(v, 'isSource', False) From fca546381217c50a4b7124424e687aa47323a243 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 23 Nov 2023 10:11:41 +0200 Subject: [PATCH 139/260] feat(translations): show translations with preview. New components: VersionBlockWithPreview, VersionBlockWithPreviewTitleLine, VersionPreviewMeta. VersionBlockWithPreview is called by VersionBlockList when rendered by TranslationBox. --- static/js/TranslationsBox.jsx | 6 +- static/js/VersionBlock.jsx | 46 ++++++++++----- static/js/VersionBlockHeader.jsx | 13 ++--- static/js/VersionBlockWithPreview.jsx | 58 +++++++++++++++++++ .../js/VersionBlockWithPreviewTitleLine.jsx | 44 ++++++++++++++ static/js/VersionPreviewMeta.jsx | 22 +++++++ 6 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 static/js/VersionBlockWithPreview.jsx create mode 100644 static/js/VersionBlockWithPreviewTitleLine.jsx create mode 100644 static/js/VersionPreviewMeta.jsx diff --git a/static/js/TranslationsBox.jsx b/static/js/TranslationsBox.jsx index f6d0410d9f..07141565cf 100644 --- a/static/js/TranslationsBox.jsx +++ b/static/js/TranslationsBox.jsx @@ -19,12 +19,12 @@ class TranslationsBox extends Component { } componentDidMount() { if(!this.isSheet()) { - Sefaria.getAllTranslationsWithText(this.props.sectionRef).then(this.onVersionsLoad); + Sefaria.getAllTranslationsWithText(this.props.srefs[0]).then(this.onVersionsLoad); } } componentDidUpdate(prevProps, prevState) { if (!this.isSheet() && prevProps.sectionRef !== this.props.sectionRef) { - Sefaria.getAllTranslationsWithText(this.props.sectionRef).then(this.onVersionsLoad); + Sefaria.getAllTranslationsWithText(this.props.srefs[0]).then(this.onVersionsLoad); } } onVersionsLoad(versions) { @@ -90,6 +90,8 @@ class TranslationsBox extends Component { viewExtendedNotes={this.props.viewExtendedNotes} inTranslationBox={true} showNotes={false} + srefs={this.props.srefs} + onRangeClick={this.props.onRangeClick} /> </> ); diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index 0ad91e1da5..fab50dc515 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -10,6 +10,7 @@ import VersionBlockHeader from "./VersionBlockHeader"; import VersionBlockSelectButton from "./VersionBlockSelectButton"; import VersionDetailsInformation from "./VersionDetailsInformation"; import VersionDetailsImage from "./VersionDetailsImage"; +import VersionBlockWithPreview from "./VersionBlockWithPreview"; class versionTools { static makeVersionTitle(version){ @@ -401,22 +402,33 @@ class VersionsBlocksList extends Component{ } { this.props.versionsByLanguages[lang].map((v) => ( - <VersionBlock - rendermode="versions-box" - sidebarDisplay={true} - version={v} - currObjectVersions={this.props.currObjectVersions} - currentRef={this.props.currentRef} - firstSectionRef={"firstSectionRef" in v ? v.firstSectionRef : null} - key={`${this.isVersionCurrent(v) ? "current" : ""}|${v.versionTitle}|${v.actualLanguage}`} - openVersionInReader={this.props.openVersionInReader} - openVersionInSidebar={this.props.openVersionInSidebar} - viewExtendedNotes={this.props.viewExtendedNotes} - isCurrent={this.isVersionCurrent(v)} - inTranslationBox={this.props.inTranslationBox} - showNotes={this.props.showNotes} - /> - )) + this.props.inTranslationBox ? + <VersionBlockWithPreview + currentRef={this.props.currentRef} + version={v} + currObjectVersions={this.props.currObjectVersions} + openVersionInReader={this.props.openVersionInReader} + openVersionInSidebar={this.props.openVersionInSidebar} + isSelected={this.isVersionCurrent(v)} + srefs={this.props.srefs} + onRangeClick={this.props.onRangeClick} + /> : + <VersionBlock + rendermode="versions-box" + sidebarDisplay={true} + version={v} + currObjectVersions={this.props.currObjectVersions} + currentRef={this.props.currentRef} + firstSectionRef={"firstSectionRef" in v ? v.firstSectionRef : null} + key={`${this.isVersionCurrent(v) ? "current" : ""}|${v.versionTitle}|${v.actualLanguage}`} + openVersionInReader={this.props.openVersionInReader} + openVersionInSidebar={this.props.openVersionInSidebar} + viewExtendedNotes={this.props.viewExtendedNotes} + isCurrent={this.isVersionCurrent(v)} + inTranslationBox={this.props.inTranslationBox} + showNotes={this.props.showNotes} + /> + )) } </div> )) @@ -437,6 +449,8 @@ VersionsBlocksList.propTypes={ showLanguageHeaders: PropTypes.bool, inTranslationBox: PropTypes.bool, showNotes: PropTypes.bool, + srefs: PropTypes.array, + onRangeClick: PropTypes.func, }; VersionsBlocksList.defaultProps = { displayCurrentVersions: true, diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlockHeader.jsx index e0bb2f933e..a16411b6d9 100644 --- a/static/js/VersionBlockHeader.jsx +++ b/static/js/VersionBlockHeader.jsx @@ -2,16 +2,16 @@ import React from 'react'; import PropTypes from "prop-types"; function VersionBlockHeader({text, link, onClick, renderMode}) { - return renderMode === 'versionTitle' ? + return renderMode === 'versionTitle' ? (<VersionBlockHeaderTitle - href={link} + link={link} onClick={onClick} versionTitle={text} />) : (<VersionBlockHeaderText - href={link} + link={link} onClick={onClick} - versionTitle={text} + text={text} />); } VersionBlockHeader.prototypes = { @@ -43,9 +43,8 @@ function VersionBlockHeaderText({link, onClick, text}) { className='versionPreview' href={link} onClick={onClick} - > - {text} - </a> + dangerouslySetInnerHTML={{__html: text}} + /> ); } VersionBlockHeaderText.prototypes = { diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlockWithPreview.jsx new file mode 100644 index 0000000000..0b517c4029 --- /dev/null +++ b/static/js/VersionBlockWithPreview.jsx @@ -0,0 +1,58 @@ +import React, {useState} from 'react'; +import PropTypes from 'prop-types'; +import VersionBlockHeader from "./VersionBlockHeader"; +import {versionTools} from './VersionBlock'; +import VersionBlockWithPreviewTitleLine from './VersionBlockWithPreviewTitleLine'; +import VersionPreviewMeta from "./VersionPreviewMeta"; +import {OpenConnectionTabButton} from "./TextList"; + +function VersionBlockWithPreview({currentRef, version, currObjectVersions, openVersionInSidebar, openVersionInReader, isSelected, srefs, onRangeClick}) { + const [isInfoOpen, setIsInfoOpen] = useState(false); + const opeInSidebar = versionTools.openVersionInSidebar.bind(null, currentRef, version, currObjectVersions, openVersionInSidebar); + function openInTabCallback(sref) { + onRangeClick(sref, false, {[version.language]: version.versionTitle}); + } + return ( + <div className='version-with-preview'> + <VersionBlockHeader + text={version.text} + onClick={opeInSidebar} + renderMode='contentText' + link={versionTools.makeVersionLink(currentRef, version, currObjectVersions, false)} + /> + <VersionBlockWithPreviewTitleLine + version={version} + currentRef={currentRef} + currObjectVersions={currObjectVersions} + openVersionInReader={openVersionInReader} + isInfoOpen={isInfoOpen} + setIsInfoOpen={setIsInfoOpen} + isSelected={isSelected} + /> + {isInfoOpen ? + <div className='version-block-with-preview-details'> + <VersionPreviewMeta + currentRef={currentRef} + version={version} + /> + <OpenConnectionTabButton + srefs={srefs} + openInTabCallback={openInTabCallback} + renderMode='versionPreview' + /> + </div> + : null} + </div> + ); +} +VersionBlockWithPreview.prototypes = { + version: PropTypes.object.isRequired, + currObjectVersions: PropTypes.object.isRequired, + currentRef: PropTypes.string.isRequired, + openVersionInSidebar: PropTypes.func, + openVersionInReader: PropTypes.func.isRequired, + isSelected: PropTypes.bool.isRequired, + srefs: PropTypes.array.isRequired, + onRangeClick: PropTypes.func.isRequired, +}; +export default VersionBlockWithPreview; diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx new file mode 100644 index 0000000000..326867b49a --- /dev/null +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import VersionBlockSelectButton from "./VersionBlockSelectButton"; +import {versionTools} from './VersionBlock'; +import Sefaria from "./sefaria/sefaria"; + +function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersions, openVersionInReader, isInfoOpen, setIsInfoOpen, isSelected}) { + function makeShortVersionTitle() { + let shortVersionTitle = version.shortVersionTitle || version.versionTitle; + if (Sefaria.interfaceLang === "english") { + shortVersionTitle = version.shortVersionTitleInHebrew || version.versionTitleInHebrew || shortVersionTitle; + } + return shortVersionTitle; + } + const chevronDirection = isInfoOpen ? 'up' : 'down'; + const showOrHide = isInfoOpen ? 'Hide' : 'Show'; + const openVersionInMoinPanel = versionTools.openVersionInMoinPanel.bind(null, currentRef, version, currObjectVersions, 'select-button', + null, openVersionInReader); + const buttonText = isSelected ? 'Currently Selected' : 'Select'; + return ( + <div className='version-with-preview-title-line'> + <a onClick={() => setIsInfoOpen(true)}> + <img src={`/static/icons/little-chevron-${chevronDirection}.svg`} alt={`${showOrHide} details`} /> + <div className='version-with-preview-short-version-title'>{makeShortVersionTitle()}</div> + </a> + <VersionBlockSelectButton + isSelected={isSelected} + openVersionInMoinPanel={openVersionInMoinPanel} + text={buttonText} + link={versionTools.makeVersionLink(currentRef, version, currObjectVersions, true)} + /> + </div> + ); +} +VersionBlockWithPreviewTitleLine.prototypes = { + currObjectVersions: PropTypes.object.isRequired, + version: PropTypes.object.isRequired, + currentRef: PropTypes.string.isRequired, + openVersionInReader: PropTypes.func.isRequired, + isInfoOpen: PropTypes.bool.isRequired, + setIsInfoOpen: PropTypes.func.isRequired, + isSelected: PropTypes.bool.isRequired, +}; +export default VersionBlockWithPreviewTitleLine; diff --git a/static/js/VersionPreviewMeta.jsx b/static/js/VersionPreviewMeta.jsx new file mode 100644 index 0000000000..2c75af4eb2 --- /dev/null +++ b/static/js/VersionPreviewMeta.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {versionTools} from "./VersionBlock"; +import VersionDetailsInformation from "./VersionDetailsInformation"; +import VersionDetailsImage from "./VersionDetailsImage"; + +function VersionPreviewMeta({currentRef, version}) { + return ( + <div className='version-preview-meta'> + <div className='version-preview-details'> + <div className='translation-version-title'>{versionTools.makeVersionTitle(version).text}</div> + <VersionDetailsInformation currentRef={currentRef} version={version}/> + </div> + <VersionDetailsImage version={version}/> + </div> + ); +} +VersionPreviewMeta.prototypes = { + currentRef: PropTypes.string.isRequired, + version: PropTypes.object.isRequired, +}; +export default VersionPreviewMeta; From f8194f98610be52098dcd684f0b2917e3ab0b31c Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Thu, 23 Nov 2023 10:26:29 +0200 Subject: [PATCH 140/260] fix: change_parent needs to force update of node full_title --- sefaria/helper/schema.py | 2 +- sefaria/model/schema.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sefaria/helper/schema.py b/sefaria/helper/schema.py index 518d62f01f..c34c1eb9d9 100644 --- a/sefaria/helper/schema.py +++ b/sefaria/helper/schema.py @@ -306,7 +306,7 @@ def change_parent(node, new_parent, place=0): old_parent.children = [n for n in old_parent.children if n.key != node.key] new_parent.children.insert(place, node) node.parent = new_parent - new_normal_form = node.ref().normal() + new_normal_form = node.ref(force_update=True).normal() index.save(override_dependencies=True) library.rebuild() diff --git a/sefaria/model/schema.py b/sefaria/model/schema.py index fe9537c7d3..a72e5c0e7e 100644 --- a/sefaria/model/schema.py +++ b/sefaria/model/schema.py @@ -1371,11 +1371,11 @@ def version_address(self): """ return self.address()[1:] - def ref(self): + def ref(self, force_update=False): from . import text d = { "index": self.index, - "book": self.full_title("en"), + "book": self.full_title("en", force_update=force_update), "primary_category": self.index.get_primary_category(), "index_node": self, "sections": [], From 0a41efad9f36b55e0f042b5412de6255b18dce9a Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Thu, 23 Nov 2023 14:00:26 +0200 Subject: [PATCH 141/260] chore: allow for exact_match --- sefaria/helper/schema.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/sefaria/helper/schema.py b/sefaria/helper/schema.py index c34c1eb9d9..c6dadbe762 100644 --- a/sefaria/helper/schema.py +++ b/sefaria/helper/schema.py @@ -278,11 +278,12 @@ def prepare_ja_for_children(ja): v.sub_content(ja.version_address(), value={}) v.save() -def change_parent(node, new_parent, place=0): +def change_parent(node, new_parent, place=0, exact_match=False): """ :param node: :param new_parent: :param place: The index of the child before which to insert, so place=0 inserts at the front of the list, and place=len(parent_node.children) inserts at the end + :param exact_match: if True, if there are two links, "X" and "Y on X", changing "X" will not also change "Y on X" :return: """ assert isinstance(node, SchemaNode) @@ -312,7 +313,10 @@ def change_parent(node, new_parent, place=0): library.rebuild() for link in linkset: - link.refs = [ref.replace(old_normal_form, new_normal_form) for ref in link.refs] + if exact_match: + link.refs = [ref.replace(old_normal_form, new_normal_form) if ref.startswith(old_normal_form) else ref for ref in link.refs] + else: + link.refs = [ref.replace(old_normal_form, new_normal_form) for ref in link.refs] link.save() # todo: commentary linkset @@ -338,7 +342,7 @@ def refresh_version_state(title): VersionState(title, {"flags": flags}) -def change_node_title(snode, old_title, lang, new_title): +def change_node_title(snode, old_title, lang, new_title, exact_match=False): """ Changes the title of snode specified by old_title and lang, to new_title. If the title changing is the primary english title, cascades to all of the impacted objects @@ -346,20 +350,26 @@ def change_node_title(snode, old_title, lang, new_title): :param old_title: :param lang: :param new_title: + :param exact_match: if True, if there are two links, "X" and "Y on X", changing "X" will not also change "Y on X" :return: """ - + old_ref = new_ref = "" def rewriter(string): - return string.replace(old_title, new_title) + return string.replace(old_ref, new_ref) + # return string.replace(old_title, new_title) def needs_rewrite(string, *args): + if exact_match: + return string.find(old_title) >= 0 and string.startswith(snode.index.title) return string.find(old_title) >= 0 and snode.index.title in string if old_title == snode.primary_title(lang=lang): + old_ref = snode.full_title('en') snode.add_title(new_title, lang, replace_primary=True, primary=True) snode.index.save() library.refresh_index_record_in_cache(snode.index) if lang == 'en': + new_ref = snode.full_title('en') cascade(snode.index.title, rewriter=rewriter, needs_rewrite=needs_rewrite) else: snode.add_title(new_title, lang) From 15bdf96af128e3cb3375571981f233e4a084544c Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Thu, 23 Nov 2023 14:28:42 +0200 Subject: [PATCH 142/260] chore: move picture and captions --- sefaria/google_storage_manager.py | 5 +++-- static/js/AdminEditor.jsx | 7 +++++-- static/js/Misc.jsx | 24 ++++++++++++------------ static/js/TopicEditor.jsx | 11 ++++++----- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/sefaria/google_storage_manager.py b/sefaria/google_storage_manager.py index 756dbc60e4..4bd2fa6c23 100644 --- a/sefaria/google_storage_manager.py +++ b/sefaria/google_storage_manager.py @@ -25,8 +25,9 @@ class GoogleStorageManager(object): @classmethod def get_bucket(cls, bucket_name): if getattr(cls, 'client', None) is None: - # for local development, change below line to cls.client = storage.Client(project="production-deployment") - cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) + # for local development, change below line to + cls.client = storage.Client(project="production-deployment") + #cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) bucket = cls.client.get_bucket(bucket_name) return bucket diff --git a/static/js/AdminEditor.jsx b/static/js/AdminEditor.jsx index 173b7c5754..1d1ecea94a 100644 --- a/static/js/AdminEditor.jsx +++ b/static/js/AdminEditor.jsx @@ -118,7 +118,7 @@ const validateMarkdownLinks = async (input) => { return true; } -const AdminEditor = ({title, data, close, catMenu, updateData, savingStatus, +const AdminEditor = ({title, data, close, catMenu, pictureUploader, updateData, savingStatus, validate, deleteObj, items = [], isNew = true, extras = [], path = []}) => { const [validatingLinks, setValidatingLinks] = useState(false); @@ -204,7 +204,10 @@ const AdminEditor = ({title, data, close, catMenu, updateData, savingStatus, return null; } else if (x === "Category Menu") { return catMenu; - } else { + } else if (x === "Picture Uploader") { + return pictureUploader; + } + else { return item({...options_for_form[x]}); } })} diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 2988f55fa7..87181e2c2c 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1609,21 +1609,21 @@ const PictureUploader = ({callback, old_filename, caption}) => { const file = fileInput.current.files[0]; if (file == null) return; - if (/\.(jpe?g|png|gif)$/i.test(file.name)) { - const reader = new FileReader(); + if (/\.(jpe?g|png|gif)$/i.test(file.name)) { + const reader = new FileReader(); - reader.addEventListener("load", function() { - uploadImage(reader.result); - }, false); + reader.addEventListener("load", function() { + uploadImage(reader.result); + }, false); - reader.addEventListener("onerror", function() { - alert(reader.error); - }, false); + reader.addEventListener("onerror", function() { + alert(reader.error); + }, false); - reader.readAsDataURL(file); - } else { - alert('not an image'); - } + reader.readAsDataURL(file); + } else { + alert('not an image'); + } } return <div className="section"> <label><InterfaceText>Picture</InterfaceText></label> diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 963f0fcac3..7e1faba58a 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -177,14 +177,15 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { const authorItems = ["English Alternate Titles", "Hebrew Alternate Titles", "Birth Place", "Hebrew Birth Place", "Birth Year", "Place of Death", "Hebrew Place of Death", "Death Year", "Era"]; authorItems.forEach(x => items.push(x)); } + items.push("Picture Uploader"); items.push("English Caption"); items.push("Hebrew Caption"); return <AdminEditor title="Topic Editor" close={close} catMenu={catMenu} data={data} savingStatus={savingStatus} - validate={validate} deleteObj={deleteObj} updateData={updateData} isNew={isNew} - items={items} extras={ - [<PictureUploader callback={handlePictureChange} old_filename={data.image_uri} - caption={{en: data.enImgCaption, he: data.heImgCaption}}/>, - isNew ? null : + validate={validate} deleteObj={deleteObj} updateData={updateData} isNew={isNew} items={items} + pictureUploader={<PictureUploader callback={handlePictureChange} old_filename={data.image_uri} + caption={{en: data.enImgCaption, he: data.heImgCaption}}/>} + extras={ + [isNew ? null : <Reorder subcategoriesAndBooks={sortedSubtopics} updateOrder={setSortedSubtopics} displayType="topics"/>, From 8e57dc3875d39973b53962511be6cbffe9460694 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Thu, 23 Nov 2023 15:03:18 +0200 Subject: [PATCH 143/260] chore: undo mistake to get_bucket --- sefaria/google_storage_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sefaria/google_storage_manager.py b/sefaria/google_storage_manager.py index 4bd2fa6c23..756dbc60e4 100644 --- a/sefaria/google_storage_manager.py +++ b/sefaria/google_storage_manager.py @@ -25,9 +25,8 @@ class GoogleStorageManager(object): @classmethod def get_bucket(cls, bucket_name): if getattr(cls, 'client', None) is None: - # for local development, change below line to - cls.client = storage.Client(project="production-deployment") - #cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) + # for local development, change below line to cls.client = storage.Client(project="production-deployment") + cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) bucket = cls.client.get_bucket(bucket_name) return bucket From 965c4eed3bf9005143c3b053e32a79ab7285f570 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 23 Nov 2023 15:48:25 +0200 Subject: [PATCH 144/260] feat(translations): styles for new feature. --- static/css/s2.css | 73 +++++++++++++++++-- static/js/VersionBlockWithPreview.jsx | 2 +- .../js/VersionBlockWithPreviewTitleLine.jsx | 2 +- static/js/VersionPreviewMeta.jsx | 6 +- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index a16596b7c5..e1e697e90a 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3515,11 +3515,61 @@ display: none; align-items: flex-start; margin-bottom: 10px; } -.versionBlock .versionTitle { +.versionBlock .versionTitle, +.versionBlock .versionPreview { font-size: 18px; color: #000; /*unicode-bidi: plaintext;*/ } +.versionBlock .versionPreview { + font-family: "Adobe Garamond Pro", "sans-serif"; +} +.versionBlock .versionPreview i.footnote { + display: none; +} +.versionBlock.with-preview { + --english-font: var(--english-sans-serif-font-family); + --hebrew-font: var(--hebrew-sans-serif-font-family); +} +.version-with-preview-title-line { + display: inline-flex; + flex-wrap: wrap; + font-size: 14px; + line-height: 22px; + color: var(--medium-grey); + margin-top: 10px; + margin-bottom: 10px; +} +.version-with-preview-title-line .open-details { + white-space: nowrap; + display: flex; + margin-right: 5px; + font-style: italic; +} +.version-with-preview-title-line img { + margin-right: 5px; +} +.version-with-preview-title-line .selectButton { + white-space: nowrap; + text-decoration: none; +} +.version-with-preview-title-line .selectButton:not(.currSelectButton) { + color: var(--select-blue); +} +.version-with-preview-title-line .selectButton::before { + content: "•"; + margin-right: 5px; + color: var(--medium-grey); +} +.version-block-with-preview-details { + background-color: var(--lighter-grey); + border-radius: 6px; + outline: 10px solid var(--lighter-grey); + margin: 10px; +} +.versionDetails-version-title { + color: black; +} .bookPage .versionBlock .versionTitle{ font-style: normal; font-weight: normal; @@ -6557,7 +6607,8 @@ But not to use a display block directive that might break continuous mode for ot .connection-buttons.access-user .connection-button.delete-link{ display:none; } -.connection-buttons .connection-button{ +.connection-buttons .connection-button, +.version-block-with-preview-details .connection-button{ font-style: normal; font-weight: normal; font-size: 13px; @@ -6570,6 +6621,11 @@ But not to use a display block directive that might break continuous mode for ot flex-flow: row wrap; align-items: center; } +.version-block-with-preview-details .connection-button { + font-size: 14px; + color: var(--dark-grey); + margin-top: 15px; +} .singlePanel .connection-buttons .connection-button{ text-align: start; margin-inline-end: 5px; @@ -6588,7 +6644,8 @@ But not to use a display block directive that might break continuous mode for ot text-align: center; font-size: 18px; } -.connection-buttons .connection-button::before{ +.connection-buttons .connection-button::before, +.version-block-with-preview-details .connection-button::before{ display: block; content: ' '; background-size: 15px 15px; @@ -6597,7 +6654,11 @@ But not to use a display block directive that might break continuous mode for ot width: 15px; margin-inline-end: 5px; } -.connection-buttons .panel-open-link::before{ +.version-block-with-preview-details .connection-button::before { + height: 18px; +} +.connection-buttons .panel-open-link::before, +.version-block-with-preview-details .connection-button::before{ background-image: url("/static/icons/open-panel.svg"); } .connection-buttons .delete-link::before{ @@ -9289,7 +9350,7 @@ body #keyboardInputMaster tbody tr td table tbody tr td.pressed{ .versionsBox .versionLanguage .versionCount { color: #999; } -.versionsBox a.selectButton { +.versionsBox a.selectButton:not(.version-with-preview-title-line .selectButton) { font-style: normal; font-weight: normal; font-size: 13px; @@ -9301,7 +9362,7 @@ body #keyboardInputMaster tbody tr td table tbody tr td.pressed{ line-height: 18px; cursor: pointer; } -.versionsBox a.selectButton.currSelectButton { +.versionsBox a.selectButton.currSelectButton:not(.version-with-preview-title-line .selectButton) { background-color: #212E50; text-decoration: none; cursor: default; diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlockWithPreview.jsx index 0b517c4029..bf8473ff46 100644 --- a/static/js/VersionBlockWithPreview.jsx +++ b/static/js/VersionBlockWithPreview.jsx @@ -13,7 +13,7 @@ function VersionBlockWithPreview({currentRef, version, currObjectVersions, openV onRangeClick(sref, false, {[version.language]: version.versionTitle}); } return ( - <div className='version-with-preview'> + <div className='versionBlock with-preview'> <VersionBlockHeader text={version.text} onClick={opeInSidebar} diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx index 326867b49a..a2b449e75b 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -19,7 +19,7 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio const buttonText = isSelected ? 'Currently Selected' : 'Select'; return ( <div className='version-with-preview-title-line'> - <a onClick={() => setIsInfoOpen(true)}> + <a className='open-details' onClick={() => setIsInfoOpen(true)}> <img src={`/static/icons/little-chevron-${chevronDirection}.svg`} alt={`${showOrHide} details`} /> <div className='version-with-preview-short-version-title'>{makeShortVersionTitle()}</div> </a> diff --git a/static/js/VersionPreviewMeta.jsx b/static/js/VersionPreviewMeta.jsx index 2c75af4eb2..2d52d197d8 100644 --- a/static/js/VersionPreviewMeta.jsx +++ b/static/js/VersionPreviewMeta.jsx @@ -6,9 +6,9 @@ import VersionDetailsImage from "./VersionDetailsImage"; function VersionPreviewMeta({currentRef, version}) { return ( - <div className='version-preview-meta'> - <div className='version-preview-details'> - <div className='translation-version-title'>{versionTools.makeVersionTitle(version).text}</div> + <div className='versionDetails preview'> + <div className='version-preview-informations'> + <div className='versionDetails-version-title'>{versionTools.makeVersionTitle(version).text}</div> <VersionDetailsInformation currentRef={currentRef} version={version}/> </div> <VersionDetailsImage version={version}/> From b2375ba6e035eba769bc63a1d268b871337735ab Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 23 Nov 2023 16:05:41 +0200 Subject: [PATCH 145/260] fix(translations): version title in Hebrew when interface is Hebrew and not the opposite. --- static/js/VersionBlockWithPreviewTitleLine.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx index a2b449e75b..5fa8b7ed4d 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -7,7 +7,7 @@ import Sefaria from "./sefaria/sefaria"; function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersions, openVersionInReader, isInfoOpen, setIsInfoOpen, isSelected}) { function makeShortVersionTitle() { let shortVersionTitle = version.shortVersionTitle || version.versionTitle; - if (Sefaria.interfaceLang === "english") { + if (Sefaria.interfaceLang === "hebrew") { shortVersionTitle = version.shortVersionTitleInHebrew || version.versionTitleInHebrew || shortVersionTitle; } return shortVersionTitle; From 2e5e4e670eae3200bcb8caa71878b777aef9a988 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 23 Nov 2023 16:17:38 +0200 Subject: [PATCH 146/260] feat(translations): ass Hebrew strings for Select and Currently Selected. --- static/js/VersionBlockSelectButton.jsx | 2 +- static/js/sefaria/strings.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/static/js/VersionBlockSelectButton.jsx b/static/js/VersionBlockSelectButton.jsx index b8f8c55bea..c1ed1bb527 100644 --- a/static/js/VersionBlockSelectButton.jsx +++ b/static/js/VersionBlockSelectButton.jsx @@ -7,7 +7,7 @@ function VersionBlockSelectButton({link, openVersionInMoinPanel, text, isSelecte href={link} onClick={openVersionInMoinPanel} > - {text} + {Sefaria._(text)} </a> ); } diff --git a/static/js/sefaria/strings.js b/static/js/sefaria/strings.js index c1998554e2..9d549c8370 100644 --- a/static/js/sefaria/strings.js +++ b/static/js/sefaria/strings.js @@ -243,6 +243,8 @@ const Strings = { "Select Version": "בחירת מהדורה", "Select Translation": "בחירת תרגום", "View in Sidebar": "פתיחת תרגום", + 'Select': 'בחירה', + 'Currently Selected': 'נוכחי', "Merged from": "נוצר ממיזוג", "Source" : "מקור", "Sources": "מקורות", From 4e5c804528745caf262ec8026570364052d9e7c8 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 23 Nov 2023 16:55:44 +0200 Subject: [PATCH 147/260] fix(translations): change right-margin to margin-inline-end for working with Hebrew. --- static/css/s2.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index e1e697e90a..6bdbeeb93e 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3543,11 +3543,11 @@ display: none; .version-with-preview-title-line .open-details { white-space: nowrap; display: flex; - margin-right: 5px; + margin-inline-end: 5px; font-style: italic; } .version-with-preview-title-line img { - margin-right: 5px; + margin-inline-end: 5px; } .version-with-preview-title-line .selectButton { white-space: nowrap; @@ -3558,7 +3558,7 @@ display: none; } .version-with-preview-title-line .selectButton::before { content: "•"; - margin-right: 5px; + margin-inline-end: 5px; color: var(--medium-grey); } .version-block-with-preview-details { From a4bfd2fe8db8dca2bc95ea28850e7cbc4db34bf1 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 23 Nov 2023 17:02:44 +0200 Subject: [PATCH 148/260] feat(translations): update TranslationBox when moving segments. --- static/js/TranslationsBox.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/TranslationsBox.jsx b/static/js/TranslationsBox.jsx index 07141565cf..31bf7d8b5d 100644 --- a/static/js/TranslationsBox.jsx +++ b/static/js/TranslationsBox.jsx @@ -23,7 +23,7 @@ class TranslationsBox extends Component { } } componentDidUpdate(prevProps, prevState) { - if (!this.isSheet() && prevProps.sectionRef !== this.props.sectionRef) { + if (!this.isSheet() && prevProps.srefs[0] !== this.props.srefs[0]) { Sefaria.getAllTranslationsWithText(this.props.srefs[0]).then(this.onVersionsLoad); } } From 203a744415e2db6437655210bdd81ffa8e40759c Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Thu, 23 Nov 2023 17:31:09 +0200 Subject: [PATCH 149/260] chore: ignore_cascade --- sefaria/helper/schema.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/sefaria/helper/schema.py b/sefaria/helper/schema.py index c6dadbe762..3073c31d41 100644 --- a/sefaria/helper/schema.py +++ b/sefaria/helper/schema.py @@ -342,7 +342,7 @@ def refresh_version_state(title): VersionState(title, {"flags": flags}) -def change_node_title(snode, old_title, lang, new_title, exact_match=False): +def change_node_title(snode, old_title, lang, new_title, ignore_cascade=False): """ Changes the title of snode specified by old_title and lang, to new_title. If the title changing is the primary english title, cascades to all of the impacted objects @@ -350,26 +350,20 @@ def change_node_title(snode, old_title, lang, new_title, exact_match=False): :param old_title: :param lang: :param new_title: - :param exact_match: if True, if there are two links, "X" and "Y on X", changing "X" will not also change "Y on X" + :param ignore_cascade: :return: """ - old_ref = new_ref = "" def rewriter(string): - return string.replace(old_ref, new_ref) - # return string.replace(old_title, new_title) + return string.replace(old_title, new_title) def needs_rewrite(string, *args): - if exact_match: - return string.find(old_title) >= 0 and string.startswith(snode.index.title) return string.find(old_title) >= 0 and snode.index.title in string if old_title == snode.primary_title(lang=lang): - old_ref = snode.full_title('en') snode.add_title(new_title, lang, replace_primary=True, primary=True) snode.index.save() library.refresh_index_record_in_cache(snode.index) - if lang == 'en': - new_ref = snode.full_title('en') + if lang == 'en' and not ignore_cascade: cascade(snode.index.title, rewriter=rewriter, needs_rewrite=needs_rewrite) else: snode.add_title(new_title, lang) From 562de7b9076dbf93f87998b28c2630f55e4f3d9d Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Thu, 23 Nov 2023 18:14:54 +0200 Subject: [PATCH 150/260] feat(translations): rtl for rel translations. --- static/js/VersionBlockHeader.jsx | 8 ++++++-- static/js/VersionBlockWithPreview.jsx | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlockHeader.jsx index a16411b6d9..5fbc7605da 100644 --- a/static/js/VersionBlockHeader.jsx +++ b/static/js/VersionBlockHeader.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from "prop-types"; -function VersionBlockHeader({text, link, onClick, renderMode}) { +function VersionBlockHeader({text, link, onClick, renderMode, direction}) { return renderMode === 'versionTitle' ? (<VersionBlockHeaderTitle link={link} @@ -12,6 +12,7 @@ function VersionBlockHeader({text, link, onClick, renderMode}) { link={link} onClick={onClick} text={text} + direction={direction} />); } VersionBlockHeader.prototypes = { @@ -19,6 +20,7 @@ VersionBlockHeader.prototypes = { text: PropTypes.string.isRequired, renderMode: PropTypes.string.isRequired, link: PropTypes.string.isRequired, + direction: PropTypes.string, }; function VersionBlockHeaderTitle({link, onClick, versionTitle}) { @@ -37,13 +39,14 @@ VersionBlockHeaderTitle.prototypes = { link: PropTypes.string.isRequired, }; -function VersionBlockHeaderText({link, onClick, text}) { +function VersionBlockHeaderText({link, onClick, text, direction}) { return ( <a className='versionPreview' href={link} onClick={onClick} dangerouslySetInnerHTML={{__html: text}} + dir={direction} /> ); } @@ -51,6 +54,7 @@ VersionBlockHeaderText.prototypes = { onClick: PropTypes.func.isRequired, versionTitle: PropTypes.string.isRequired, link: PropTypes.string.isRequired, + direction: PropTypes.string.isRequired, }; export default VersionBlockHeader; diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlockWithPreview.jsx index bf8473ff46..caff81b728 100644 --- a/static/js/VersionBlockWithPreview.jsx +++ b/static/js/VersionBlockWithPreview.jsx @@ -19,6 +19,7 @@ function VersionBlockWithPreview({currentRef, version, currObjectVersions, openV onClick={opeInSidebar} renderMode='contentText' link={versionTools.makeVersionLink(currentRef, version, currObjectVersions, false)} + direction={version.direction || 'ltr'} /> <VersionBlockWithPreviewTitleLine version={version} From 6237432c22374f0bb7aaa2373ec0eb3cd66fbebf Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 26 Nov 2023 09:03:09 +0200 Subject: [PATCH 151/260] fix(translations): click chevron to close. --- static/js/VersionBlockWithPreviewTitleLine.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx index 5fa8b7ed4d..1936152f31 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -19,7 +19,7 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio const buttonText = isSelected ? 'Currently Selected' : 'Select'; return ( <div className='version-with-preview-title-line'> - <a className='open-details' onClick={() => setIsInfoOpen(true)}> + <a className='open-details' onClick={() => setIsInfoOpen(!isInfoOpen)}> <img src={`/static/icons/little-chevron-${chevronDirection}.svg`} alt={`${showOrHide} details`} /> <div className='version-with-preview-short-version-title'>{makeShortVersionTitle()}</div> </a> From 565c00acfcbf45ad6a6971b40f29db4bf444d8fb Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 26 Nov 2023 09:03:59 +0200 Subject: [PATCH 152/260] refactor(translations): remove superfluous css rules. --- static/css/s2.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index 6bdbeeb93e..6ed79714d1 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3541,7 +3541,6 @@ display: none; margin-bottom: 10px; } .version-with-preview-title-line .open-details { - white-space: nowrap; display: flex; margin-inline-end: 5px; font-style: italic; @@ -3550,7 +3549,6 @@ display: none; margin-inline-end: 5px; } .version-with-preview-title-line .selectButton { - white-space: nowrap; text-decoration: none; } .version-with-preview-title-line .selectButton:not(.currSelectButton) { From b1962404e0c766c15fb523fc8e0a3008df39436e Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 26 Nov 2023 09:06:46 +0200 Subject: [PATCH 153/260] refactor(translations): replace logic of 'if component else null' by && logic. --- static/js/VersionBlockWithPreview.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlockWithPreview.jsx index caff81b728..42d61acd2a 100644 --- a/static/js/VersionBlockWithPreview.jsx +++ b/static/js/VersionBlockWithPreview.jsx @@ -30,7 +30,7 @@ function VersionBlockWithPreview({currentRef, version, currObjectVersions, openV setIsInfoOpen={setIsInfoOpen} isSelected={isSelected} /> - {isInfoOpen ? + {isInfoOpen && <div className='version-block-with-preview-details'> <VersionPreviewMeta currentRef={currentRef} @@ -41,8 +41,7 @@ function VersionBlockWithPreview({currentRef, version, currObjectVersions, openV openInTabCallback={openInTabCallback} renderMode='versionPreview' /> - </div> - : null} + </div>} </div> ); } From 9918da99f238da1c1721a838c23926a21fb53cba Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Sun, 26 Nov 2023 10:17:13 +0200 Subject: [PATCH 154/260] chore: undo unnecessary change to ProfilePic --- static/js/Misc.jsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index b6e3a12b60..6d57aeb1d6 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -262,11 +262,10 @@ class ProfilePic extends Component { if (response.error) { throw new Error(response.error); } else { - const defaultCallback = () => { + this.closePopup({ cb: () => { window.location = "/profile/" + Sefaria.slug; // reload to get update return; - }; - this.closePopup({ cb: this.props.saveCallback ? this.props.saveCallback(response.urls[0]) : defaultCallback}); + }}); } } catch (e) { errored = true; @@ -354,7 +353,6 @@ class ProfilePic extends Component { } ProfilePic.propTypes = { url: PropTypes.string, - saveCallback: PropTypes.func, // used by AdminEditor to override default callback upon save name: PropTypes.string, len: PropTypes.number, hideOnDefault: PropTypes.bool, // hide profile pic if you have are displaying default pic From 07d460529592f8a95386ddfbfe0265647e09accf Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Sun, 26 Nov 2023 11:15:05 +0200 Subject: [PATCH 155/260] fix(Topic Editor): resize images as thumbnails 300x300 --- reader/views.py | 5 +++-- static/js/TopicEditor.jsx | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/reader/views.py b/reader/views.py index 868e030af2..39bfd36c24 100644 --- a/reader/views.py +++ b/reader/views.py @@ -64,7 +64,7 @@ get_random_topic_source, edit_topic_source, \ update_order_of_topic_sources, delete_ref_topic_link, update_authors_place_and_time, add_image_to_topic from sefaria.helper.community_page import get_community_page_items -from sefaria.helper.file import get_resized_file +from sefaria.helper.file import get_resized_file, thumbnail_image_file from sefaria.image_generator import make_img_http_response import sefaria.tracker as tracker @@ -3571,12 +3571,13 @@ def topic_upload_photo(request): import base64 bucket_name = GoogleStorageManager.TOPICS_BUCKET img_file_in_mem = BytesIO(base64.b64decode(request.POST.get('file'))) + img = Image.open(img_file_in_mem) + img_file_in_mem = thumbnail_image_file(img, (300, 300)) old_filename = request.POST.get('old_filename') if old_filename: old_filename = f"topics/{old_filename.split('/')[-1]}" img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"topics/{request.user.id}-{uuid.uuid1()}.gif", bucket_name, old_filename=old_filename) - #img_url = 'https://storage.googleapis.com/img.sefaria.org/topics/41861-683e06f6-891a-11ee-be47-4a26184f1ad1.gif' return jsonResponse({"url": img_url}) return jsonResponse({"error": "Unsupported HTTP method."}) diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 7e1faba58a..7cb58e97e3 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -84,6 +84,14 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { alert(Sefaria._("Title must be provided.")); return false; } + if (data.enImgCaption.length > 200) { + alert("English caption is too long. It should not be more than 200 characters"); + return false; + } + if (data.heImgCaption.length > 200) { + alert("Hebrew caption is too long. It should not be more than 200 characters") + return false; + } if (sortedSubtopics.length > 0 && !isNew) { await saveReorderedSubtopics(); // make sure subtopics reordered before saving topic information below } From 860451249e6a827810b17d8bf9ff654a0ffe3377 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 26 Nov 2023 13:09:36 +0200 Subject: [PATCH 156/260] feat(translations): truncate translation with expanding ellipsis. --- static/css/s2.css | 15 +++++++++++++-- static/js/VersionBlockHeader.jsx | 23 ++++++++++++++++++++--- static/js/VersionBlockWithPreview.jsx | 2 +- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index 6ed79714d1..4f30b5c69a 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3516,14 +3516,19 @@ display: none; margin-bottom: 10px; } .versionBlock .versionTitle, -.versionBlock .versionPreview { +.versionBlock .versionPreviewWithOptionalEllipsis { font-size: 18px; color: #000; /*unicode-bidi: plaintext;*/ } .versionBlock .versionPreview { + --line-height: 22px; + line-height: var(--line-height); font-family: "Adobe Garamond Pro", "sans-serif"; } +.versionBlock .versionPreview big{ + font-size: inherit; +} .versionBlock .versionPreview i.footnote { display: none; } @@ -3531,6 +3536,11 @@ display: none; --english-font: var(--english-sans-serif-font-family); --hebrew-font: var(--hebrew-sans-serif-font-family); } +.versionBlock .versionPreview.shouldAttemptTruncation { + overflow: hidden; + --max-lines: 5; + max-height: calc(var(--line-height) * var(--max-lines)); +} .version-with-preview-title-line { display: inline-flex; flex-wrap: wrap; @@ -5613,7 +5623,8 @@ But not to use a display block directive that might break continuous mode for ot font-family: "Heebo", sans-serif; } /* Footnotes */ -.segment sup { +.segment sup, +.versionPreview sup { margin-left: .2em; margin-right: .2em; text-decoration: none; diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlockHeader.jsx index 5fbc7605da..d4ff9c3b8c 100644 --- a/static/js/VersionBlockHeader.jsx +++ b/static/js/VersionBlockHeader.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import PropTypes from "prop-types"; function VersionBlockHeader({text, link, onClick, renderMode, direction}) { @@ -40,14 +40,31 @@ VersionBlockHeaderTitle.prototypes = { }; function VersionBlockHeaderText({link, onClick, text, direction}) { + const [shouldAttemptTruncation, setShouldAttemptTruncation] = useState(true); + const [truncationOccurred, setTruncationOccurred] = useState(false); + const textRef = useRef(null); + useEffect(() => { + const element = textRef.current; + const computedStyles = window.getComputedStyle(element); + const maxHeight = parseInt(computedStyles.getPropertyValue('max-height'), 10); + setTruncationOccurred(element.scrollHeight > maxHeight); + }, []); //[] for running in resize seems better than adding a listener + function onEllipsisClick() { + setShouldAttemptTruncation(false); + setTruncationOccurred(false); + } return ( - <a - className='versionPreview' + <div className='versionPreviewWithOptionalEllipsis'> + <div + className={`versionPreview ${shouldAttemptTruncation && 'shouldAttemptTruncation'}`} + ref={textRef} href={link} onClick={onClick} dangerouslySetInnerHTML={{__html: text}} dir={direction} /> + {truncationOccurred && <a className='ellipsis' onClick={onEllipsisClick}>…</a>} + </div> ); } VersionBlockHeaderText.prototypes = { diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlockWithPreview.jsx index 42d61acd2a..953ae4c95e 100644 --- a/static/js/VersionBlockWithPreview.jsx +++ b/static/js/VersionBlockWithPreview.jsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import VersionBlockHeader from "./VersionBlockHeader"; import {versionTools} from './VersionBlock'; From 437c0664ed7e5408c8d8c23d40881b27d1d34375 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Sun, 26 Nov 2023 15:12:56 +0200 Subject: [PATCH 157/260] chore: add validation for aspect ratio --- reader/views.py | 10 ++++++++-- static/js/Misc.jsx | 11 +++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/reader/views.py b/reader/views.py index 39bfd36c24..6c9479423e 100644 --- a/reader/views.py +++ b/reader/views.py @@ -3571,9 +3571,15 @@ def topic_upload_photo(request): import base64 bucket_name = GoogleStorageManager.TOPICS_BUCKET img_file_in_mem = BytesIO(base64.b64decode(request.POST.get('file'))) + + # validate img has correct aspect ratio img = Image.open(img_file_in_mem) - img_file_in_mem = thumbnail_image_file(img, (300, 300)) - old_filename = request.POST.get('old_filename') + aspect_ratio = float(img.width)/img.height + if aspect_ratio < 0.65: + return jsonResponse({"error": f"Width-to-height ratio is {aspect_ratio}. The ratio must be at least 0.65."}) + img_file_in_mem.seek(0) + + old_filename = request.POST.get('old_filename') # delete file from google storage if there is one there if old_filename: old_filename = f"topics/{old_filename.split('/')[-1]}" img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"topics/{request.user.id}-{uuid.uuid1()}.gif", diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 6d57aeb1d6..c2881c86a6 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1596,10 +1596,13 @@ const PictureUploader = ({callback, old_filename, caption}) => { contentType: false, processData: false, success: function(data) { - callback(data.url); - }, + if (data.error) { + alert(data.error); + } else { + callback(data.url); + }}, error: function(e) { - console.log("photo upload ERROR", e); + alert(e); } }); } @@ -1620,7 +1623,7 @@ const PictureUploader = ({callback, old_filename, caption}) => { reader.readAsDataURL(file); } else { - alert('not an image'); + alert('The file is not an image'); } } return <div className="section"> From be94119a92ec756f4b25458d0e84b8b6c25260ef Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 26 Nov 2023 16:02:30 +0200 Subject: [PATCH 158/260] feat(translations): chevron svg files. --- static/icons/little-chevron-down.svg | 9 +++++++++ static/icons/little-chevron-up.svg | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 static/icons/little-chevron-down.svg create mode 100644 static/icons/little-chevron-up.svg diff --git a/static/icons/little-chevron-down.svg b/static/icons/little-chevron-down.svg new file mode 100644 index 0000000000..5cef2fecad --- /dev/null +++ b/static/icons/little-chevron-down.svg @@ -0,0 +1,9 @@ +<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<rect x="8" width="8" height="8" transform="rotate(90 8 0)" fill="url(#pattern0)" fill-opacity="0.4"/> +<defs> +<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image0_704_284" transform="scale(0.00416667)"/> +</pattern> +<image id="image0_704_284" width="240" height="240" xlink:href="data:image/png;base64,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"/> +</defs> +</svg> diff --git a/static/icons/little-chevron-up.svg b/static/icons/little-chevron-up.svg new file mode 100644 index 0000000000..b99c21a478 --- /dev/null +++ b/static/icons/little-chevron-up.svg @@ -0,0 +1,9 @@ +<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<rect y="8" width="8" height="8" transform="rotate(-90 0 8)" fill="url(#pattern0)" fill-opacity="0.4"/> +<defs> +<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image0_704_282" transform="scale(0.00416667)"/> +</pattern> +<image id="image0_704_282" width="240" height="240" xlink:href="data:image/png;base64,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"/> +</defs> +</svg> From 59fa18f4fbfa18c5fbe519373dd5d93b4e2396e6 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 27 Nov 2023 09:58:16 +0200 Subject: [PATCH 159/260] refactor(translations): use contentText rather than specific rules. --- static/css/s2.css | 9 +++++---- static/js/VersionBlockHeader.jsx | 3 +-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index 4f30b5c69a..6374137b58 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3515,18 +3515,19 @@ display: none; align-items: flex-start; margin-bottom: 10px; } -.versionBlock .versionTitle, -.versionBlock .versionPreviewWithOptionalEllipsis { +.versionBlock .versionTitle { font-size: 18px; color: #000; /*unicode-bidi: plaintext;*/ } +.versionBlock.with-preview .versionPreviewWithOptionalEllipsis { + display: block; +} .versionBlock .versionPreview { --line-height: 22px; line-height: var(--line-height); - font-family: "Adobe Garamond Pro", "sans-serif"; } -.versionBlock .versionPreview big{ +.versionBlock .versionPreview big { font-size: inherit; } .versionBlock .versionPreview i.footnote { diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlockHeader.jsx index d4ff9c3b8c..6222a9ec25 100644 --- a/static/js/VersionBlockHeader.jsx +++ b/static/js/VersionBlockHeader.jsx @@ -54,14 +54,13 @@ function VersionBlockHeaderText({link, onClick, text, direction}) { setTruncationOccurred(false); } return ( - <div className='versionPreviewWithOptionalEllipsis'> + <div className={`versionPreviewWithOptionalEllipsis contentText ${direction==='ltr' ? 'en' : 'he'}`} dir={direction}> <div className={`versionPreview ${shouldAttemptTruncation && 'shouldAttemptTruncation'}`} ref={textRef} href={link} onClick={onClick} dangerouslySetInnerHTML={{__html: text}} - dir={direction} /> {truncationOccurred && <a className='ellipsis' onClick={onEllipsisClick}>…</a>} </div> From 445e64201420d004b2d1e40a0129d187057ff1c2 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Mon, 27 Nov 2023 13:25:50 +0200 Subject: [PATCH 160/260] chore: DELETE image with topic editor started --- reader/views.py | 26 ++++++++++++++++---------- static/js/Misc.jsx | 17 +++++++++++++---- static/js/TopicEditor.jsx | 2 +- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/reader/views.py b/reader/views.py index 6c9479423e..ead137e1f4 100644 --- a/reader/views.py +++ b/reader/views.py @@ -3563,15 +3563,24 @@ def profile_follow_api(request, ftype, slug): @catch_error_as_json def topic_upload_photo(request): + from io import BytesIO + import uuid + import base64 if not request.user.is_authenticated: return jsonResponse({"error": _("You must be logged in to update a topic photo.")}) - if request.method == "POST": - from io import BytesIO - import uuid - import base64 - bucket_name = GoogleStorageManager.TOPICS_BUCKET - img_file_in_mem = BytesIO(base64.b64decode(request.POST.get('file'))) - + bucket_name = GoogleStorageManager.TOPICS_BUCKET + file = request.POST.get('file') + old_filename = request.POST.get('old_filename') # delete file from google storage if there is one there + if old_filename: + old_filename = f"topics/{old_filename.split('/')[-1]}" + if request.method == "DELETE": + if file == "": + if old_filename is None: + return jsonResponse({"error": "You cannot remove an image as you haven't selected one yet."}) + GoogleStorageManager.delete_filename(old_filename, bucket_name) + return jsonResponse({"success": "You have successfully removed the image."}) + elif request.method == "POST": + img_file_in_mem = BytesIO(base64.b64decode(file)) # validate img has correct aspect ratio img = Image.open(img_file_in_mem) aspect_ratio = float(img.width)/img.height @@ -3579,9 +3588,6 @@ def topic_upload_photo(request): return jsonResponse({"error": f"Width-to-height ratio is {aspect_ratio}. The ratio must be at least 0.65."}) img_file_in_mem.seek(0) - old_filename = request.POST.get('old_filename') # delete file from google storage if there is one there - if old_filename: - old_filename = f"topics/{old_filename.split('/')[-1]}" img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"topics/{request.user.id}-{uuid.uuid1()}.gif", bucket_name, old_filename=old_filename) return jsonResponse({"url": img_url}) diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index c2881c86a6..75e7cb8b33 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1583,7 +1583,7 @@ const PictureUploader = ({callback, old_filename, caption}) => { */ const fileInput = useRef(null); - var uploadImage = function(imageData) { + const uploadImage = function(imageData, type="POST") { const formData = new FormData(); formData.append('file', imageData.replace(/data:image\/(jpe?g|png|gif);base64,/, "")); if (old_filename !== "") { @@ -1591,7 +1591,7 @@ const PictureUploader = ({callback, old_filename, caption}) => { } $.ajax({ url: Sefaria.apiHost + "/api/topics/upload-image", - type: 'POST', + type, data: formData, contentType: false, processData: false, @@ -1599,7 +1599,11 @@ const PictureUploader = ({callback, old_filename, caption}) => { if (data.error) { alert(data.error); } else { - callback(data.url); + if (data.url) { + callback(data.url); + } else if (data.success) { + alert(data.success); + } }}, error: function(e) { alert(e); @@ -1631,7 +1635,12 @@ const PictureUploader = ({callback, old_filename, caption}) => { <div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton"> <label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"></label> </div><input id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} /> - {old_filename !== "" && <div style={{"max-width": "420px"}}><br/><ImageWithCaption photoLink={old_filename} caption={caption}/></div>} + {old_filename !== "" && <div style={{"max-width": "420px"}}> + <div onClick={() => uploadImage("", "DELETE")} id="saveAccountSettings" className="button small blue control-elem" tabIndex="0" role="button"> + <InterfaceText>Remove Picture</InterfaceText> + </div> + <br/><ImageWithCaption photoLink={old_filename} caption={caption}/></div> + } </div> } diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 7cb58e97e3..753dfc5244 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -109,7 +109,7 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { postData.altTitles.en = data.enAltTitles.map(x => x.name); // alt titles implemented using TitleVariants which contains list of objects with 'name' property. postData.altTitles.he = data.heAltTitles.map(x => x.name); - if (data.image_uri !== "") { + if (data.image_uri !== "" || data.enImgCaption !== "" || data.heImgCaption !== "") { postData.image = {"image_uri": data.image_uri, "image_caption": {"en": data.enImgCaption, "he": data.heImgCaption}} } // add descriptions if they changed From 1a09b2fc8f63322c201e13c4ed27daaac50c9bbd Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 27 Nov 2023 15:03:43 +0200 Subject: [PATCH 161/260] fix(translations): all elements of VersionBlockWithPreviewWTitleLine inline. --- static/css/s2.css | 4 ++-- static/js/VersionBlockWithPreviewTitleLine.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index 6374137b58..3122e451b2 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3543,7 +3543,7 @@ display: none; max-height: calc(var(--line-height) * var(--max-lines)); } .version-with-preview-title-line { - display: inline-flex; + display: inline; flex-wrap: wrap; font-size: 14px; line-height: 22px; @@ -3552,7 +3552,7 @@ display: none; margin-bottom: 10px; } .version-with-preview-title-line .open-details { - display: flex; + display: inline; margin-inline-end: 5px; font-style: italic; } diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx index 1936152f31..f18001d83e 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -21,7 +21,7 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio <div className='version-with-preview-title-line'> <a className='open-details' onClick={() => setIsInfoOpen(!isInfoOpen)}> <img src={`/static/icons/little-chevron-${chevronDirection}.svg`} alt={`${showOrHide} details`} /> - <div className='version-with-preview-short-version-title'>{makeShortVersionTitle()}</div> + <span className='version-with-preview-short-version-title'>{makeShortVersionTitle()}</span> </a> <VersionBlockSelectButton isSelected={isSelected} From ea2cf71701a8392986d087b77aec0ad240760e4e Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 27 Nov 2023 16:30:13 +0200 Subject: [PATCH 162/260] refactoe(translations): chevron image in css rather than jsx. --- static/css/s2.css | 8 +++++++- static/js/VersionBlockWithPreviewTitleLine.jsx | 4 +--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index 3122e451b2..d2fc3dd8c0 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3556,7 +3556,13 @@ display: none; margin-inline-end: 5px; font-style: italic; } -.version-with-preview-title-line img { +.version-with-preview-title-line .open-details.chevron-up::before { + content: url('/static/icons/little-chevron-up.svg'); +} +.version-with-preview-title-line .open-details.chevron-down::before { + content: url('/static/icons/little-chevron-down.svg'); +} +.version-with-preview-title-line .open-details::before { margin-inline-end: 5px; } .version-with-preview-title-line .selectButton { diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx index f18001d83e..2f24c2799c 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -13,14 +13,12 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio return shortVersionTitle; } const chevronDirection = isInfoOpen ? 'up' : 'down'; - const showOrHide = isInfoOpen ? 'Hide' : 'Show'; const openVersionInMoinPanel = versionTools.openVersionInMoinPanel.bind(null, currentRef, version, currObjectVersions, 'select-button', null, openVersionInReader); const buttonText = isSelected ? 'Currently Selected' : 'Select'; return ( <div className='version-with-preview-title-line'> - <a className='open-details' onClick={() => setIsInfoOpen(!isInfoOpen)}> - <img src={`/static/icons/little-chevron-${chevronDirection}.svg`} alt={`${showOrHide} details`} /> + <a className={`open-details chevron-${chevronDirection}`} onClick={() => setIsInfoOpen(!isInfoOpen)}> <span className='version-with-preview-short-version-title'>{makeShortVersionTitle()}</span> </a> <VersionBlockSelectButton From b7af63679085f2a0846c77eded25fc6ace921fda Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Tue, 28 Nov 2023 13:59:09 +0200 Subject: [PATCH 163/260] feat(Topic Editor): remove image button --- reader/views.py | 30 ++++++++++++++++++------------ sefaria/helper/topic.py | 6 +++++- sefaria/urls.py | 2 ++ static/js/Misc.jsx | 26 +++++++++++++++++++------- static/js/TopicEditor.jsx | 7 ++++++- 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/reader/views.py b/reader/views.py index ead137e1f4..6846950a46 100644 --- a/reader/views.py +++ b/reader/views.py @@ -3561,6 +3561,20 @@ def profile_follow_api(request, ftype, slug): return jsonResponse(response) return jsonResponse({"error": "Unsupported HTTP method."}) + +@catch_error_as_json +def topic_delete_photo(request, file): + if not request.user.is_authenticated: + return jsonResponse({"error": _("You must be logged in to update a topic photo.")}) + bucket_name = GoogleStorageManager.TOPICS_BUCKET + if file: + file = f"topics/{file.split('/')[-1]}" + if request.method == "DELETE": + if file is None: + return jsonResponse({"error": "You cannot remove an image as you haven't selected one yet."}) + GoogleStorageManager.delete_filename(file, bucket_name) + return jsonResponse({"success": "You have successfully removed the image."}) + @catch_error_as_json def topic_upload_photo(request): from io import BytesIO @@ -3574,20 +3588,12 @@ def topic_upload_photo(request): if old_filename: old_filename = f"topics/{old_filename.split('/')[-1]}" if request.method == "DELETE": - if file == "": - if old_filename is None: - return jsonResponse({"error": "You cannot remove an image as you haven't selected one yet."}) - GoogleStorageManager.delete_filename(old_filename, bucket_name) - return jsonResponse({"success": "You have successfully removed the image."}) + if old_filename is None: + return jsonResponse({"error": "You cannot remove an image as you haven't selected one yet."}) + GoogleStorageManager.delete_filename(old_filename, bucket_name) + return jsonResponse({"success": "You have successfully removed the image."}) elif request.method == "POST": img_file_in_mem = BytesIO(base64.b64decode(file)) - # validate img has correct aspect ratio - img = Image.open(img_file_in_mem) - aspect_ratio = float(img.width)/img.height - if aspect_ratio < 0.65: - return jsonResponse({"error": f"Width-to-height ratio is {aspect_ratio}. The ratio must be at least 0.65."}) - img_file_in_mem.seek(0) - img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"topics/{request.user.id}-{uuid.uuid1()}.gif", bucket_name, old_filename=old_filename) return jsonResponse({"url": img_url}) diff --git a/sefaria/helper/topic.py b/sefaria/helper/topic.py index a50b71ce74..d9c3bcd41b 100644 --- a/sefaria/helper/topic.py +++ b/sefaria/helper/topic.py @@ -1106,7 +1106,11 @@ def update_topic(topic, **kwargs): topic.change_description(kwargs.get("description", None), kwargs.get("categoryDescription", None)) if "image" in kwargs: - topic.image = kwargs["image"] + image_dict = kwargs["image"] + if image_dict["image_uri"] != "": + topic.image = kwargs["image"] + elif hasattr(topic, 'image'): + del topic.image topic.save() diff --git a/sefaria/urls.py b/sefaria/urls.py index 929467238c..c1ae9404f9 100644 --- a/sefaria/urls.py +++ b/sefaria/urls.py @@ -104,6 +104,8 @@ url(r'^topics/(?P<topic>.+)$', reader_views.topic_page), url(r'^api/topic/completion/(?P<topic>.+)', reader_views.topic_completion_api), url(r'^api/topics/upload-image$', reader_views.topic_upload_photo), + url(r'^api/topics/delete-image/(?P<file>.+)$', reader_views.topic_delete_photo), + ] # Calendar Redirects diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 75e7cb8b33..da34eeb963 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1601,8 +1601,6 @@ const PictureUploader = ({callback, old_filename, caption}) => { } else { if (data.url) { callback(data.url); - } else if (data.success) { - alert(data.success); } }}, error: function(e) { @@ -1630,16 +1628,30 @@ const PictureUploader = ({callback, old_filename, caption}) => { alert('The file is not an image'); } } + const deleteImage = () => { + const old_filename_wout_url = old_filename.split("/").slice(-1); + const url = `${Sefaria.apiHost}/api/topics/delete-image/${old_filename_wout_url}`; + requestWithCallBack({url, type: "DELETE", redirect: () => alert("Deleted image.")}); + callback(""); + fileInput.current.value = ""; + } return <div className="section"> <label><InterfaceText>Picture</InterfaceText></label> + <label> + <span className="optional"><InterfaceText>Please use horizontal, square, or only-slightly-vertical images for best results.</InterfaceText></span> + </label> <div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton"> - <label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"></label> - </div><input id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} /> + <label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"> + <div className="button small blue control-elem" tabIndex="0" role="button"> + <InterfaceText>Upload Picture</InterfaceText> + </div> + </label> + </div><input style={{visibility: "hidden"}} id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} /> {old_filename !== "" && <div style={{"max-width": "420px"}}> - <div onClick={() => uploadImage("", "DELETE")} id="saveAccountSettings" className="button small blue control-elem" tabIndex="0" role="button"> + <br/><ImageWithCaption photoLink={old_filename} caption={caption}/> + <br/><div onClick={deleteImage} className="button extraSmall blue control-elem" tabIndex="1" role="button"> <InterfaceText>Remove Picture</InterfaceText> - </div> - <br/><ImageWithCaption photoLink={old_filename} caption={caption}/></div> + </div></div> } </div> } diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 753dfc5244..7fed81d4f8 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -109,9 +109,14 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { postData.altTitles.en = data.enAltTitles.map(x => x.name); // alt titles implemented using TitleVariants which contains list of objects with 'name' property. postData.altTitles.he = data.heAltTitles.map(x => x.name); - if (data.image_uri !== "" || data.enImgCaption !== "" || data.heImgCaption !== "") { + // add image if image or caption changed + const origImageURI = origData?.origImage?.image_uri || ""; + const origEnCaption = origData?.origImage?.image_caption?.en || ""; + const origHeCaption = origData?.origImage?.image_caption?.he || ""; + if (data.image_uri !== origImageURI || data.enImgCaption !== origEnCaption || data.heImgCaption !== origHeCaption) { postData.image = {"image_uri": data.image_uri, "image_caption": {"en": data.enImgCaption, "he": data.heImgCaption}} } + // add descriptions if they changed const origDescription = {en: origData?.origEnDescription || "", he: origData?.origHeDescription || ""}; const origCategoryDescription = {en: origData?.origEnCategoryDescription || "", he: origData?.origHeCategoryDescription || ""}; From f6f3dba39dcf277f5a8987452507bcbf5dd42e6d Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Tue, 28 Nov 2023 16:45:46 +0200 Subject: [PATCH 164/260] fix(translations): fix font for versionPreviewWithOptionalEllipsis. --- static/css/s2.css | 3 +++ static/js/VersionBlockHeader.jsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/static/css/s2.css b/static/css/s2.css index d2fc3dd8c0..a16516ba76 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3521,6 +3521,9 @@ display: none; /*unicode-bidi: plaintext;*/ } .versionBlock.with-preview .versionPreviewWithOptionalEllipsis { + --english-font: "adobe-garamond-pro", var(--english-serif-font-family); + --hebrew-font: var(--hebrew-serif-font-family); + font-size: 18px; display: block; } .versionBlock .versionPreview { diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlockHeader.jsx index 6222a9ec25..d61b718bdf 100644 --- a/static/js/VersionBlockHeader.jsx +++ b/static/js/VersionBlockHeader.jsx @@ -54,7 +54,7 @@ function VersionBlockHeaderText({link, onClick, text, direction}) { setTruncationOccurred(false); } return ( - <div className={`versionPreviewWithOptionalEllipsis contentText ${direction==='ltr' ? 'en' : 'he'}`} dir={direction}> + <div className={'versionPreviewWithOptionalEllipsis'} dir={direction}> <div className={`versionPreview ${shouldAttemptTruncation && 'shouldAttemptTruncation'}`} ref={textRef} From f5bab3020b132e1fb155a16eb6ded456b3b4c9b9 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Tue, 28 Nov 2023 17:48:05 +0200 Subject: [PATCH 165/260] fix(translations): fix everything for elements that should have cursor pointer will have it. --- static/css/s2.css | 1 + static/js/VersionBlock.jsx | 2 +- static/js/VersionBlockHeader.jsx | 2 +- static/js/VersionBlockWithPreviewTitleLine.jsx | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index a16516ba76..dda88b522d 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3541,6 +3541,7 @@ display: none; --hebrew-font: var(--hebrew-sans-serif-font-family); } .versionBlock .versionPreview.shouldAttemptTruncation { + display: block; overflow: hidden; --max-lines: 5; max-height: calc(var(--line-height) * var(--max-lines)); diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index fab50dc515..aaba6924f8 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -29,7 +29,7 @@ class versionTools { const withParam = mainPanel ? "" : "&with=Translation Open"; const versionParam = mainPanel ? version.language : 'side'; const nonSelectedVersionParams = Object.entries(currObjectVersions) - .filter(([vlang, ver]) => !!ver && !!ver?.versionTitle && !version?.merged && (withParam || vlang !== version.language)) // in 'side' case, keep all version params + .filter(([vlang, ver]) => !!ver && !!ver?.versionTitle && !version?.merged && (withParam || vlang === version.language)) // in 'side' case, keep all version params .map(([vlang, ver]) => `&v${vlang}=${ver.versionTitle.replace(/\s/g,'_')}`) .join(""); const versionLink = nonSelectedVersionParams === "" ? null : `/${Sefaria.normRef(currRef)}${nonSelectedVersionParams}&v${versionParam}=${version.versionTitle.replace(/\s/g,'_')}${withParam}`.replace("&","?"); diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlockHeader.jsx index d61b718bdf..6bcc015464 100644 --- a/static/js/VersionBlockHeader.jsx +++ b/static/js/VersionBlockHeader.jsx @@ -55,7 +55,7 @@ function VersionBlockHeaderText({link, onClick, text, direction}) { } return ( <div className={'versionPreviewWithOptionalEllipsis'} dir={direction}> - <div + <a className={`versionPreview ${shouldAttemptTruncation && 'shouldAttemptTruncation'}`} ref={textRef} href={link} diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx index 2f24c2799c..8201f6b707 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -18,8 +18,8 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio const buttonText = isSelected ? 'Currently Selected' : 'Select'; return ( <div className='version-with-preview-title-line'> - <a className={`open-details chevron-${chevronDirection}`} onClick={() => setIsInfoOpen(!isInfoOpen)}> - <span className='version-with-preview-short-version-title'>{makeShortVersionTitle()}</span> + <a className={`open-details chevron-${chevronDirection}`} onClick={() => setIsInfoOpen(!isInfoOpen)} href='#'> + {makeShortVersionTitle()} </a> <VersionBlockSelectButton isSelected={isSelected} From 3dbaa1a48cdf5e3675ec64e7ac88882a67ec651d Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Tue, 28 Nov 2023 19:14:09 +0200 Subject: [PATCH 166/260] fix(translations): typo. --- static/js/VersionBlock.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index aaba6924f8..4ed71caadd 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -204,7 +204,7 @@ class VersionBlock extends Component { const openVersionInSidebar = versionTools.openVersionInSidebar.bind(null, this.props.currentRef, this.props.version, this.props.currObjectVersions, this.props.openVersionInSidebar); const openVersionInMoinPanel = versionTools.openVersionInMoinPanel.bind(null, this.props.currentRef, - this.props.version, this.props.currObjectVersions, this.props.renderMode, this.props.firstSectionRef, this.props.openVersionInReader); + this.props.version, this.props.currObjectVersions, this.props.rendermode, this.props.firstSectionRef, this.props.openVersionInReader); if (this.state.editing && Sefaria.is_moderator) { // Editing View From f74f953e491c593ed81c3cd17b3f3008081131f8 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Tue, 28 Nov 2023 19:18:01 +0200 Subject: [PATCH 167/260] fix(translations): default cursor for 'currently selected'. --- static/css/s2.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static/css/s2.css b/static/css/s2.css index dda88b522d..571f469146 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3572,6 +3572,9 @@ display: none; .version-with-preview-title-line .selectButton { text-decoration: none; } +.version-with-preview-title-line .selectButton.currSelectButton { + cursor: default; +} .version-with-preview-title-line .selectButton:not(.currSelectButton) { color: var(--select-blue); } From 74e0dfbd3498eb71abfe0081b20d6686ffafdb39 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 29 Nov 2023 11:53:02 +0200 Subject: [PATCH 168/260] refactor(translations): typo. --- static/js/VersionBlock.jsx | 8 ++++---- static/js/VersionBlockSelectButton.jsx | 4 ++-- static/js/VersionBlockWithPreviewTitleLine.jsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index 4ed71caadd..2e3b5afd0d 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -53,7 +53,7 @@ class versionTools { } openVersionInSidebar(version.versionTitle, version.language); } - static openVersionInMoinPanel(currRef, version, currObjectVersions, renderMode, firstSectionRef, openVersionInReader, e) { + static openVersionInMainPanel(currRef, version, currObjectVersions, renderMode, firstSectionRef, openVersionInReader, e) { e.preventDefault(); try { gtag("event", "onClick_select_version", {element_name: `select_version`, @@ -203,7 +203,7 @@ class VersionBlock extends Component { const showLanguagLabel = this.props.rendermode == "book-page"; const openVersionInSidebar = versionTools.openVersionInSidebar.bind(null, this.props.currentRef, this.props.version, this.props.currObjectVersions, this.props.openVersionInSidebar); - const openVersionInMoinPanel = versionTools.openVersionInMoinPanel.bind(null, this.props.currentRef, + const openVersionInMainPanel = versionTools.openVersionInMainPanel.bind(null, this.props.currentRef, this.props.version, this.props.currObjectVersions, this.props.rendermode, this.props.firstSectionRef, this.props.openVersionInReader); if (this.state.editing && Sefaria.is_moderator) { @@ -276,7 +276,7 @@ class VersionBlock extends Component { <div className="versionTitle" role="heading"> <VersionBlockHeader text={vtitle["text"]} - onClick={this.props.rendermode === 'book-page' ? openVersionInMoinPanel : openVersionInSidebar} + onClick={this.props.rendermode === 'book-page' ? openVersionInMainPanel : openVersionInSidebar} renderMode='versionTitle' link={versionTools.makeVersionLink(this.props.currentRef, this.props.version, this.props.currObjectVersions, this.props.rendermode === 'book-page')} @@ -288,7 +288,7 @@ class VersionBlock extends Component { <div className="versionSelect sans-serif"> <VersionBlockSelectButton isSelected={this.props.isCurrent} - openVersionInMoinPanel={openVersionInMoinPanel} + openVersionInMainPanel={openVersionInMainPanel} text={this.makeSelectVersionLanguage()} link={versionTools.makeVersionLink(this.props.currentRef, this.props.version, this.props.currObjectVersions, true)} diff --git a/static/js/VersionBlockSelectButton.jsx b/static/js/VersionBlockSelectButton.jsx index c1ed1bb527..74cb1ee9f4 100644 --- a/static/js/VersionBlockSelectButton.jsx +++ b/static/js/VersionBlockSelectButton.jsx @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from "prop-types"; -function VersionBlockSelectButton({link, openVersionInMoinPanel, text, isSelected}) { +function VersionBlockSelectButton({link, openVersionInMainPanel, text, isSelected}) { return ( <a className={`selectButton ${isSelected ? "currSelectButton" : ""}`} href={link} - onClick={openVersionInMoinPanel} + onClick={openVersionInMainPanel} > {Sefaria._(text)} </a> diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx index 8201f6b707..09e1b56122 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -13,7 +13,7 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio return shortVersionTitle; } const chevronDirection = isInfoOpen ? 'up' : 'down'; - const openVersionInMoinPanel = versionTools.openVersionInMoinPanel.bind(null, currentRef, version, currObjectVersions, 'select-button', + const openVersionInMainPanel = versionTools.openVersionInMainPanel.bind(null, currentRef, version, currObjectVersions, 'select-button', null, openVersionInReader); const buttonText = isSelected ? 'Currently Selected' : 'Select'; return ( @@ -23,7 +23,7 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio </a> <VersionBlockSelectButton isSelected={isSelected} - openVersionInMoinPanel={openVersionInMoinPanel} + openVersionInMainPanel={openVersionInMainPanel} text={buttonText} link={versionTools.makeVersionLink(currentRef, version, currObjectVersions, true)} /> From a2d7305a8e4698c4be0450d251dba96c403cf51d Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Wed, 29 Nov 2023 12:30:40 +0200 Subject: [PATCH 169/260] fix(Admin Editor): Add places that are neighborhoods or villages --- sefaria/model/place.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/model/place.py b/sefaria/model/place.py index 6f5b2729b5..dd42ff7f47 100644 --- a/sefaria/model/place.py +++ b/sefaria/model/place.py @@ -64,7 +64,7 @@ def create_new_place(cls, en, he=None): def city_to_coordinates(self, city): geolocator = Nominatim(user_agent='hello@sefaria.org') location = geolocator.geocode(city) - if location and location.raw['type'] in ['administrative', 'city', 'town', 'municipality']: + if location and location.raw['type'] in ['administrative', 'city', 'town', 'municipality', 'neighbourhood', 'village']: self.point_location(lon=location.longitude, lat=location.latitude) else: raise InputError(f"{city} is not a real city.") From 0b43a860f7f0505cb97d82bd60f2cf2d2ea0104b Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 29 Nov 2023 14:16:49 +0200 Subject: [PATCH 170/260] feat(translations): border line only after language. --- static/css/s2.css | 5 +++++ static/js/VersionBlock.jsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/static/css/s2.css b/static/css/s2.css index 571f469146..25c6657b3c 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3601,8 +3601,13 @@ display: none; } .versionsBox .versionBlock { padding: 20px 0; +} +.versionsBox .versionBlock:not(.with-preview) { border-top: solid 1px #CCC; } +.language-block .versionLanguage { + border-bottom: solid 1px #CCC; +} .bookPage .versionsBox .versionBlock{ padding-top: 20px; padding-bottom: 34px; diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index 2e3b5afd0d..1e4164925d 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -392,7 +392,7 @@ class VersionsBlocksList extends Component{ <div className="versionsBox"> { sortedLanguages.map((lang) => ( - <div key={lang}> + <div className="language-block" key={lang}> { this.props.showLanguageHeaders ? <div className="versionLanguage sans-serif"> {Sefaria._(Sefaria.translateISOLanguageCode(lang))}<span className="enInHe connectionsCount">{` (${this.props.versionsByLanguages[lang].length})`}</span> From 2d081dbc5bd5d9b54e752372c188b329bbab468c Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 29 Nov 2023 14:27:14 +0200 Subject: [PATCH 171/260] feat(translations): space between version title and image. --- static/css/s2.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static/css/s2.css b/static/css/s2.css index 25c6657b3c..1cb5f8d278 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -6654,6 +6654,9 @@ But not to use a display block directive that might break continuous mode for ot color: var(--dark-grey); margin-top: 15px; } +.version-block-with-preview-details img { + padding-inline-start: 15px; +} .singlePanel .connection-buttons .connection-button{ text-align: start; margin-inline-end: 5px; From 51f6c8bcedb2f9c1d3f341cbd87e8375ed7495fd Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 29 Nov 2023 15:01:47 +0200 Subject: [PATCH 172/260] fix(translations): replace margin and outline by padding. --- static/css/s2.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index 1cb5f8d278..8d0b3ee447 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3586,8 +3586,7 @@ display: none; .version-block-with-preview-details { background-color: var(--lighter-grey); border-radius: 6px; - outline: 10px solid var(--lighter-grey); - margin: 10px; + padding: 10px; } .versionDetails-version-title { color: black; From ed77c69899498205556c40d046dae198750bd4f7 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 29 Nov 2023 15:32:34 +0200 Subject: [PATCH 173/260] fix(translations): make ReaderPanel.selectVersion work on mobile. --- static/js/ReaderApp.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/static/js/ReaderApp.jsx b/static/js/ReaderApp.jsx index 3dd75d07eb..41dc82f487 100644 --- a/static/js/ReaderApp.jsx +++ b/static/js/ReaderApp.jsx @@ -1345,10 +1345,12 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { const { dependentPanel, isDependentPanelConnections } = this._getDependentPanel(n); // make sure object reference changes for setState() - dependentPanel.currVersions = {...panel.currVersions}; panel.currVersions = {...panel.currVersions}; + if (this.props.multiPanel) { //there is no dependentPanel in mobile + dependentPanel.currVersions = {...panel.currVersions}; - dependentPanel.settings.language = this._getPanelLangOnVersionChange(dependentPanel, versionLanguage, isDependentPanelConnections); + dependentPanel.settings.language = this._getPanelLangOnVersionChange(dependentPanel, versionLanguage, isDependentPanelConnections); + } this.setState({panels: this.state.panels}); } navigatePanel(n, ref, currVersions={en: null, he: null}) { From 95923ce8a2b20b6e8909578a8680735550692fe9 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Wed, 29 Nov 2023 15:51:17 +0200 Subject: [PATCH 174/260] chore: fix CSS of Upload picture button --- sefaria/google_storage_manager.py | 5 +++-- static/js/Misc.jsx | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/sefaria/google_storage_manager.py b/sefaria/google_storage_manager.py index 756dbc60e4..4bd2fa6c23 100644 --- a/sefaria/google_storage_manager.py +++ b/sefaria/google_storage_manager.py @@ -25,8 +25,9 @@ class GoogleStorageManager(object): @classmethod def get_bucket(cls, bucket_name): if getattr(cls, 'client', None) is None: - # for local development, change below line to cls.client = storage.Client(project="production-deployment") - cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) + # for local development, change below line to + cls.client = storage.Client(project="production-deployment") + #cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) bucket = cls.client.get_bucket(bucket_name) return bucket diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index da34eeb963..de1c881ba1 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1640,13 +1640,13 @@ const PictureUploader = ({callback, old_filename, caption}) => { <label> <span className="optional"><InterfaceText>Please use horizontal, square, or only-slightly-vertical images for best results.</InterfaceText></span> </label> - <div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton"> - <label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"> - <div className="button small blue control-elem" tabIndex="0" role="button"> + <div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton"> + <label htmlFor="addImageFileSelector"> + <div className="button extraSmall blue control-elem" tabIndex="0" role="button"> <InterfaceText>Upload Picture</InterfaceText> </div> </label> - </div><input style={{visibility: "hidden"}} id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} /> + </div><input style={{display: "none"}} id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} /> {old_filename !== "" && <div style={{"max-width": "420px"}}> <br/><ImageWithCaption photoLink={old_filename} caption={caption}/> <br/><div onClick={deleteImage} className="button extraSmall blue control-elem" tabIndex="1" role="button"> From 6d8152de199a9c1146a49b9a2c47e2858fbb713a Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Thu, 30 Nov 2023 11:40:15 +0200 Subject: [PATCH 175/260] chore: revert get_bucket --- sefaria/google_storage_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sefaria/google_storage_manager.py b/sefaria/google_storage_manager.py index 4bd2fa6c23..756dbc60e4 100644 --- a/sefaria/google_storage_manager.py +++ b/sefaria/google_storage_manager.py @@ -25,9 +25,8 @@ class GoogleStorageManager(object): @classmethod def get_bucket(cls, bucket_name): if getattr(cls, 'client', None) is None: - # for local development, change below line to - cls.client = storage.Client(project="production-deployment") - #cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) + # for local development, change below line to cls.client = storage.Client(project="production-deployment") + cls.client = storage.Client.from_service_account_json(GOOGLE_APPLICATION_CREDENTIALS_FILEPATH) bucket = cls.client.get_bucket(bucket_name) return bucket From e8b35c3894833b72a834d7570337fd60b98f9a03 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Thu, 30 Nov 2023 15:44:14 +0200 Subject: [PATCH 176/260] test: Temporarily disable crm tests that were never properly written --- sefaria/helper/crm/tests/crm_connection_manager_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sefaria/helper/crm/tests/crm_connection_manager_test.py b/sefaria/helper/crm/tests/crm_connection_manager_test.py index ab8c4182fc..7186bf3ddc 100644 --- a/sefaria/helper/crm/tests/crm_connection_manager_test.py +++ b/sefaria/helper/crm/tests/crm_connection_manager_test.py @@ -3,7 +3,7 @@ from sefaria.helper.crm.nationbuilder import NationbuilderConnectionManager from sefaria.helper.crm.salesforce import SalesforceConnectionManager - +""" class TestConnectionTest(TestCase): def __init__(self): self.nb_connection = NationbuilderConnectionManager() @@ -12,3 +12,4 @@ def __init__(self): def test_subscribes_user(self): for connection in self.connections: +""" \ No newline at end of file From f07d43fb55960d2e0c3323c4c05c00af2d5b422b Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Thu, 30 Nov 2023 19:20:53 +0200 Subject: [PATCH 177/260] fix: Try to connect to mongo replica set using primaryPreferred read preference This is to try and see if this affects sandbox - database connectivity and discoverability --- sefaria/system/database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sefaria/system/database.py b/sefaria/system/database.py index ff0f07b66e..6d24183dc9 100644 --- a/sefaria/system/database.py +++ b/sefaria/system/database.py @@ -41,9 +41,9 @@ def get_test_db(): # and also escape user/pass username = urllib.parse.quote_plus(SEFARIA_DB_USER) password = urllib.parse.quote_plus(SEFARIA_DB_PASSWORD) - connection_uri = 'mongodb://{}:{}@{}/?ssl=false&readPreference=secondaryPreferred&replicaSet={}'.format(username, password, MONGO_HOST, MONGO_REPLICASET_NAME) + connection_uri = 'mongodb://{}:{}@{}/?ssl=false&readPreference=primaryPreferred&replicaSet={}'.format(username, password, MONGO_HOST, MONGO_REPLICASET_NAME) else: - connection_uri = 'mongodb://{}/?ssl=false&readPreference=secondaryPreferred&replicaSet={}'.format(MONGO_HOST, MONGO_REPLICASET_NAME) + connection_uri = 'mongodb://{}/?ssl=false&readPreference=primaryPreferred&replicaSet={}'.format(MONGO_HOST, MONGO_REPLICASET_NAME) # Now connect to the mongo server client = pymongo.MongoClient(connection_uri) From e14b966f8074bf776cfc3711ba189115faddcd6b Mon Sep 17 00:00:00 2001 From: Brendan Galloway <brendan@flanksource.com> Date: Fri, 1 Dec 2023 13:06:59 +0200 Subject: [PATCH 178/260] ci: rework pytest launcher to read running spec and prevent drift --- .github/workflows/continuous.yaml | 4 +--- build/ci/createJobFromRollout.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100755 build/ci/createJobFromRollout.sh diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index d025215746..e0a71c824a 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -309,12 +309,10 @@ jobs: | sed 's/[^a-z0-9\.\-]//g') >> $GITHUB_OUTPUT - name: Start Job - run: envsubst '${GITHUB_RUN_ID},${DEPLOY_ENV},${WEB_IMAGE_NAME},${WEB_IMAGE_TAG},${TIMESTAMP}' < ./build/ci/pyTestPod.yaml | kubectl apply -f - + run: ./build/ci/createJobFromRollout.sh $GITHUB_RUN_ID $DEPLOY_ENV env: # dependent on GITHUB_RUN_ID, which is implicitly passed in DEPLOY_ENV: sandbox-${{ steps.get-sha.outputs.sha_short }} - WEB_IMAGE_NAME: us-east1-docker.pkg.dev/${{secrets.DEV_PROJECT}}/containers/sefaria-web-${{ steps.branch-name.outputs.current_branch }} - WEB_IMAGE_TAG: sha-${{ steps.get-sha.outputs.sha_short }} - name: Wait For Job To Finish run: ./build/ci/waitForCIJob.bash timeout-minutes: 60 diff --git a/build/ci/createJobFromRollout.sh b/build/ci/createJobFromRollout.sh new file mode 100755 index 0000000000..83095909b0 --- /dev/null +++ b/build/ci/createJobFromRollout.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +GITHUB_RUN_ID=$1 +DEPLOY_ENV=$2 + +cat << EOF > job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + labels: + ci-run: $GITHUB_RUN_ID + test-name: pytest + name: $DEPLOY_ENV-pytest-sandbox-$GITHUB_RUN_ID +spec: + backoffLimit: 2 # in waitForCIJob, we look for 2 fails before declaring failure. This could be made a variable. + template: + metadata: + labels: + ci-run: $GITHUB_RUN_ID + test-name: pytest + spec: +EOF + +kubectl get rollout $DEPLOY_ENV-web -o yaml | yq '.spec.template.spec' > spec.yaml +yq -i '.spec.template.spec += load("spec.yaml")' job.yaml +yq -i '.spec.template.spec.containers[0].args = ["-c", "pip3 install pytest-django; pytest -v -m \"not deep and not failing\" ./sefaria; echo $? > /dev/stdout; exit 0;"]' job.yaml + +kubectl apply -f job.yaml From b38a9224a75700a913f4086afa1a9366e9d47e8c Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Sun, 3 Dec 2023 14:57:03 +0200 Subject: [PATCH 179/260] fix(Topic Editor): dont allow "Cancel" (only allow "Save") if removed/uploaded image --- static/js/TopicEditor.jsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 7fed81d4f8..28f49cc3ce 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -33,11 +33,20 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { .filter(x => x.slug !== origData.origSlug) // dont include topics that are self-linked || []); const [isChanged, setIsChanged] = useState(false); + const [changedPicture, setChangedPicture] = useState(false); const toggle = function() { setSavingStatus(savingStatus => !savingStatus); } + const closeTopicEditor = (e) => { + if (changedPicture) { + alert("You changed the topic picture, and therefore, you must save your topic changes."); + return; + } + close(e); + } + const handleCatChange = function(e) { data.catSlug = e.target.value; @@ -174,6 +183,7 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { } const handlePictureChange = (url) => { data["image_uri"] = url; + setChangedPicture(true); updateData({...data}); } @@ -193,7 +203,7 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { items.push("Picture Uploader"); items.push("English Caption"); items.push("Hebrew Caption"); - return <AdminEditor title="Topic Editor" close={close} catMenu={catMenu} data={data} savingStatus={savingStatus} + return <AdminEditor title="Topic Editor" close={closeTopicEditor} catMenu={catMenu} data={data} savingStatus={savingStatus} validate={validate} deleteObj={deleteObj} updateData={updateData} isNew={isNew} items={items} pictureUploader={<PictureUploader callback={handlePictureChange} old_filename={data.image_uri} caption={{en: data.enImgCaption, he: data.heImgCaption}}/>} From 2386a9fd1228195894ed2a158123e59074a1facf Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Sun, 3 Dec 2023 15:05:25 +0200 Subject: [PATCH 180/260] ci: Fix for error parsing job manifest file Fixes: 'error: unable to decode "job.yaml": json: cannot unmarshal number into Go struct field ObjectMeta.metadata.labels of type string' --- build/ci/createJobFromRollout.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ci/createJobFromRollout.sh b/build/ci/createJobFromRollout.sh index 83095909b0..85b0723dda 100755 --- a/build/ci/createJobFromRollout.sh +++ b/build/ci/createJobFromRollout.sh @@ -8,7 +8,7 @@ apiVersion: batch/v1 kind: Job metadata: labels: - ci-run: $GITHUB_RUN_ID + ci-run: "${GITHUB_RUN_ID}" test-name: pytest name: $DEPLOY_ENV-pytest-sandbox-$GITHUB_RUN_ID spec: From 463ff17009b2f9ee1e2a3b1d6e3b6ba35e570ede Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Sun, 3 Dec 2023 16:41:12 +0200 Subject: [PATCH 181/260] ci: Fix for error parsing job manifest file #2 --- build/ci/createJobFromRollout.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ci/createJobFromRollout.sh b/build/ci/createJobFromRollout.sh index 85b0723dda..d2812abb5b 100755 --- a/build/ci/createJobFromRollout.sh +++ b/build/ci/createJobFromRollout.sh @@ -16,7 +16,7 @@ spec: template: metadata: labels: - ci-run: $GITHUB_RUN_ID + ci-run: "${GITHUB_RUN_ID}" test-name: pytest spec: EOF From a7f390c23d689ce57293c105681bc7e67d83593a Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Sun, 3 Dec 2023 19:44:23 +0200 Subject: [PATCH 182/260] ci: Try to add restartPolicy to the pytest job's spec. This is to address the error: 'The Job "<sandbox-id>" is invalid: spec.template.spec.restartPolicy: Required value: valid values: "OnFailure", "Never"' --- build/ci/createJobFromRollout.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build/ci/createJobFromRollout.sh b/build/ci/createJobFromRollout.sh index d2812abb5b..c56baf1d25 100755 --- a/build/ci/createJobFromRollout.sh +++ b/build/ci/createJobFromRollout.sh @@ -23,6 +23,7 @@ EOF kubectl get rollout $DEPLOY_ENV-web -o yaml | yq '.spec.template.spec' > spec.yaml yq -i '.spec.template.spec += load("spec.yaml")' job.yaml +yq -i '.spec.template.spec.restartPolicy = Never' job.yaml yq -i '.spec.template.spec.containers[0].args = ["-c", "pip3 install pytest-django; pytest -v -m \"not deep and not failing\" ./sefaria; echo $? > /dev/stdout; exit 0;"]' job.yaml kubectl apply -f job.yaml From 61b7807cf2033926e8d0e5cc3f2dec714c5343e2 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Mon, 4 Dec 2023 02:24:34 -0500 Subject: [PATCH 183/260] Reorganize code to be within different scope and add some documentation --- static/js/StaticPages.jsx | 278 +++++++++++++++++++------------------- 1 file changed, 140 insertions(+), 138 deletions(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 3d1a835b15..a2154125b0 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2640,8 +2640,9 @@ const ConditionalLink = ({ link, children }) => * Team Page */ - - +// Takes an array and boolean proposition function to be evaluated against each element +// Returns two arrays within an array +// The first contains the elements for which the proposition function evaluates to true. The second contains the rest const partition = (arr, prop) => arr.reduce( (accumulator, currentValue) => { @@ -2651,156 +2652,157 @@ const partition = (arr, prop) => [[], []] ); -const TeamMembersPage = memo(() => { - const byLastName = () => { - const locale = Sefaria.interfaceLang === "hebrew" ? "he" : "en"; - return (a, b) => { - const lastNameA = a.teamMemberDetails.teamName[locale].split(" ").pop(); - const lastNameB = b.teamMemberDetails.teamName[locale].split(" ").pop(); - return lastNameA.localeCompare(lastNameB, locale); - }; +// Defines a comparator to be used for sorting team members +const byLastName = () => { + const locale = Sefaria.interfaceLang === "hebrew" ? "he" : "en"; + return (a, b) => { + const lastNameA = a.teamMemberDetails.teamName[locale].split(" ").pop(); + const lastNameB = b.teamMemberDetails.teamName[locale].split(" ").pop(); + return lastNameA.localeCompare(lastNameB, locale); }; - - const TeamTitle = ({ teamTitle }) => ( - <div className="teamTitle"> - <InterfaceText text={teamTitle} /> - </div> - ); - - const TeamName = ({ teamName }) => ( - <div className="teamName"> - <InterfaceText text={teamName} /> - </div> - ); - - const TeamMemberDetails = ({ teamMemberDetails }) => ( - <div className="teamMemberDetails"> - <TeamName teamName={teamMemberDetails.teamName} /> - <TeamTitle teamTitle={teamMemberDetails.teamTitle} /> - </div> - ); - - const TeamMemberImage = ({ teamMember }) => ( - <div className="teamMemberImage"> - <img - src={teamMember.teamMemberImage} - alt={`Headshot of ${teamMember.teamMemberDetails.teamName.en}`} - /> - </div> +}; + +const TeamTitle = ({ teamTitle }) => ( + <div className="teamTitle"> + <InterfaceText text={teamTitle} /> + </div> +); + +const TeamName = ({ teamName }) => ( + <div className="teamName"> + <InterfaceText text={teamName} /> + </div> +); + +const TeamMemberDetails = ({ teamMemberDetails }) => ( + <div className="teamMemberDetails"> + <TeamName teamName={teamMemberDetails.teamName} /> + <TeamTitle teamTitle={teamMemberDetails.teamTitle} /> + </div> +); + +const TeamMemberImage = ({ teamMember }) => ( + <div className="teamMemberImage"> + <img + src={teamMember.teamMemberImage} + alt={`Headshot of ${teamMember.teamMemberDetails.teamName.en}`} + /> + </div> +); + +const TeamMember = ({ teamMember }) => ( + <div className="teamMember"> + <TeamMemberImage teamMember={teamMember} /> + <TeamMemberDetails teamMemberDetails={teamMember.teamMemberDetails} /> + </div> +); + +const TeamMembers = ({ teamMembers }) => ( + <> + {teamMembers.map((teamMember) => ( + <TeamMember key={teamMember.id} teamMember={teamMember} /> + ))} + </> +); + +const BoardMember = ({ boardMember }) => ( + <div className="teamBoardMember"> + <TeamMemberDetails teamMemberDetails={boardMember.teamMemberDetails} /> + </div> +); + +const BoardMembers = ({ boardMembers }) => { + let chairmanBoardMember; + const chairmanIndex = boardMembers.findIndex( + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === + "chairman" ); - - const TeamMember = ({ teamMember }) => ( - <div className="teamMember"> - <TeamMemberImage teamMember={teamMember} /> - <TeamMemberDetails teamMemberDetails={teamMember.teamMemberDetails} /> - </div> + if (chairmanIndex !== -1) { + chairmanBoardMember = boardMembers.splice(chairmanIndex, 1); + } + const [cofounderBoardMembers, regularBoardMembers] = partition( + boardMembers, + (boardMember) => + boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === + "co-founder" ); - - const TeamMembers = ({ teamMembers }) => ( + + return ( <> - {teamMembers.map((teamMember) => ( - <TeamMember key={teamMember.id} teamMember={teamMember} /> + {chairmanBoardMember && ( + <BoardMember boardMember={chairmanBoardMember[0]} /> + )} + {cofounderBoardMembers.map((boardMember) => ( + <BoardMember key={boardMember.id} boardMember={boardMember} /> + ))} + {regularBoardMembers.sort(byLastName()).map((boardMember) => ( + <BoardMember key={boardMember.id} boardMember={boardMember} /> ))} </> ); - - const BoardMember = ({ boardMember }) => ( - <div className="teamBoardMember"> - <TeamMemberDetails teamMemberDetails={boardMember.teamMemberDetails} /> - </div> - ); - - const BoardMembers = ({ boardMembers }) => { - let chairmanBoardMember; - const chairmanIndex = boardMembers.findIndex( - (boardMember) => - boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === - "chairman" - ); - if (chairmanIndex !== -1) { - chairmanBoardMember = boardMembers.splice(chairmanIndex, 1); +}; + +const fetchTeamMembersJSON = async () => { + const query = ` +query { + teamMembers(pagination: { limit: -1 }) { + data { + id + attributes { + teamName + teamTitle + isTeamBoardMember + teamMemberImage { + data { + attributes { + url + } + } } - const [cofounderBoardMembers, regularBoardMembers] = partition( - boardMembers, - (boardMember) => - boardMember.teamMemberDetails.teamTitle.en.toLowerCase() === - "co-founder" - ); - - return ( - <> - {chairmanBoardMember && ( - <BoardMember boardMember={chairmanBoardMember[0]} /> - )} - {cofounderBoardMembers.map((boardMember) => ( - <BoardMember key={boardMember.id} boardMember={boardMember} /> - ))} - {regularBoardMembers.sort(byLastName()).map((boardMember) => ( - <BoardMember key={boardMember.id} boardMember={boardMember} /> - ))} - </> - ); - }; + localizations { + data { + attributes { + locale + teamName + teamTitle + } + } + } + } + } + } +} +`; + try { + const response = await fetch(STRAPI_INSTANCE + "/graphql", { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "omit", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify({ query }), + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + throw error; + } +}; +const TeamMembersPage = memo(() => { const [ordinaryTeamMembers, setOrdinaryTeamMembers] = useState([]); const [teamBoardMembers, setTeamBoardMembers] = useState([]); const [error, setError] = useState(null); useEffect(() => { - const fetchTeamMembersJSON = async () => { - const query = ` - query { - teamMembers(pagination: { limit: -1 }) { - data { - id - attributes { - teamName - teamTitle - isTeamBoardMember - teamMemberImage { - data { - attributes { - url - } - } - } - localizations { - data { - attributes { - locale - teamName - teamTitle - } - } - } - } - } - } - } - `; - try { - const response = await fetch(STRAPI_INSTANCE + "/graphql", { - method: "POST", - mode: "cors", - cache: "no-cache", - credentials: "omit", - headers: { - "Content-Type": "application/json", - }, - redirect: "follow", - referrerPolicy: "no-referrer", - body: JSON.stringify({ query }), - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.statusText}`); - } - const data = await response.json(); - return data; - } catch (error) { - throw error; - } - }; - const loadTeamMembers = async () => { if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { try { From 4ff856f47f697121faa7eaaa163cfb6f918506bc Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Mon, 4 Dec 2023 03:32:45 -0500 Subject: [PATCH 184/260] Reorganize code and move grouping of the jobs by department to where the data is retrieved --- static/js/StaticPages.jsx | 324 +++++++++++++++++++------------------- 1 file changed, 163 insertions(+), 161 deletions(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index a72ee6948a..e0c6460d43 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2640,170 +2640,162 @@ const ConditionalLink = ({ link, children }) => * Jobs Page */ -const JobsPage = memo(() => { +// Show a different header with a description of Sefaria for the page in the case that there are jobs available +const JobsPageHeader = ({ jobsAreAvailable }) => { + return ( + <> + <header> + <h1 className="serif"> + <span className="int-en">Jobs at Sefaria</span> + <span className="int-he">משרות פנויות בספריא</span> + </h1> - // Show a different header with a description of Sefaria for the page in the case that there are jobs available - const JobsPageHeader = ({ jobsAreAvailable }) => { - return ( - <> - <header> - <h1 className="serif"> - <span className="int-en">Jobs at Sefaria</span> - <span className="int-he">משרות פנויות בספריא</span> - </h1> - - {jobsAreAvailable ? ( - <> - <h2> - <span className="int-en">About Sefaria</span> - <span className="int-he">אודות ספריא</span> - </h2> - <p> - <span className="int-en"> - Sefaria is a non-profit organization dedicated to creating the - future of Torah in an open and participatory way. We are assembling - a free, living library of Jewish texts and their interconnections, - in Hebrew and in translation. - </span> - <span className="int-he"> - ספריא היא ארגון ללא מטרות רווח שמטרתו יצירת הדור הבא של לימוד התורה - באופן פתוח ומשותף. אנחנו בספריא מרכיבים ספרייה חיה וחופשית של טקסטים - יהודיים וכלל הקישורים ביניהם, בשפת המקור ובתרגומים. - </span> - </p> - </> - ) : null} - </header> - </> - ); - }; - - const Job = ({ job }) => { - return ( - <div className="job"> - <a className="joblink" target="_blank" href={job.jobLink}> - {job.jobDescription} - </a> - </div> - ); - }; - - // Show the list of job postings within a department category - const JobsListForDepartment = ({ jobsList }) => { - return ( - <section className="jobsListForDepartment"> - {jobsList.map((job) => ( - <Job key={job.id} job={job} /> - ))} - </section> - ); - }; - - // Job postings are grouped by department. This component will show the jobs for a specific department - // Each department has a header for its category before showing a list of the job postings there - const JobPostingsByDepartment = ({ department, departmentJobPostings }) => { - return ( - <section className="section department englishOnly"> - <header> - <h2 className="anchorable">{department}</h2> - </header> - <JobsListForDepartment key={department} jobsList={departmentJobPostings} /> - </section> - ); - }; - - // Show all the job postings, but first group them by department and render each department separately - const JobPostings = ({ jobPostings }) => { - const groupedJobPostings = jobPostings.reduce((jobPostingsGroupedByDepartment, jobPosting) => { - const category = jobPosting.jobDepartmentCategory; - if (!jobPostingsGroupedByDepartment[category]) { - jobPostingsGroupedByDepartment[category] = []; - } - jobPostingsGroupedByDepartment[category].push(jobPosting); - return jobPostingsGroupedByDepartment; - }, {}); - - return ( - Object.entries(groupedJobPostings).map(([department, departmentJobPostings]) => { - return ( - <JobPostingsByDepartment - key={department} - department={department} - departmentJobPostings={departmentJobPostings} - /> - ); - }) - ); - }; - - - const NoJobsNotice = () => { - return ( - <div className="section nothing"> - <p> - <span className="int-en"> - Sefaria does not currently have any open positions. - Please follow us on <a target="_blank" href="http://www.facebook.com/sefaria.org" >Facebook</a> - to hear about our next openings. - </span> - <span className="int-he"> - ספריא איננה מחפשת כעת עובדים חדשים. - עקבו אחרינו ב<a target="_blank" href="http://www.facebook.com/sefaria.org" >פייסבוק</a>  - כדי להשאר מעודכנים במשרות עתידיות. - </span> - </p> - </div> - ); - }; + {jobsAreAvailable ? ( + <> + <h2> + <span className="int-en">About Sefaria</span> + <span className="int-he">אודות ספריא</span> + </h2> + <p> + <span className="int-en"> + Sefaria is a non-profit organization dedicated to creating the + future of Torah in an open and participatory way. We are assembling + a free, living library of Jewish texts and their interconnections, + in Hebrew and in translation. + </span> + <span className="int-he"> + ספריא היא ארגון ללא מטרות רווח שמטרתו יצירת הדור הבא של לימוד התורה + באופן פתוח ומשותף. אנחנו בספריא מרכיבים ספרייה חיה וחופשית של טקסטים + יהודיים וכלל הקישורים ביניהם, בשפת המקור ובתרגומים. + </span> + </p> + </> + ) : null} + </header> + </> + ); +}; - const [jobPostings, setJobPostings] = useState([]); - const [error, setError] = useState(null); +const Job = ({ job }) => { + return ( + <div className="job"> + <a className="joblink" target="_blank" href={job.jobLink}> + {job.jobDescription} + </a> + </div> + ); +}; - const fetchJobsJSON = async () => { - const currentDateTime = new Date().toISOString(); - const query = ` - query { - jobPostings( - pagination: { limit: -1 } - filters: { - jobPostingStartDate: { lte: \"${currentDateTime}\" } - jobPostingEndDate: { gte: \"${currentDateTime}\" } - } - ) { - data { - id - attributes { - jobLink - jobDescription - jobDepartmentCategory - } +// Show the list of job postings within a department category +const JobsListForDepartment = ({ jobsList }) => { + return ( + <section className="jobsListForDepartment"> + {jobsList.map((job) => ( + <Job key={job.id} job={job} /> + ))} + </section> + ); +}; + +// Job postings are grouped by department. This component will show the jobs for a specific department +// Each department has a header for its category before showing a list of the job postings there +const JobPostingsByDepartment = ({ department, departmentJobPostings }) => { + return ( + <section className="section department englishOnly"> + <header> + <h2 className="anchorable">{department}</h2> + </header> + <JobsListForDepartment key={department} jobsList={departmentJobPostings} /> + </section> + ); +}; + +// Show all the job postings grouped by department and render each department separately +const GroupedJobPostings = ({ groupedJobPostings }) => { + + return ( + Object.entries(groupedJobPostings).map(([department, departmentJobPostings]) => { + return ( + <JobPostingsByDepartment + key={department} + department={department} + departmentJobPostings={departmentJobPostings} + /> + ); + }) + ); +}; + + +const NoJobsNotice = () => { + return ( + <div className="section nothing"> + <p> + <span className="int-en"> + Sefaria does not currently have any open positions. + Please follow us on <a target="_blank" href="http://www.facebook.com/sefaria.org" >Facebook</a> + to hear about our next openings. + </span> + <span className="int-he"> + ספריא איננה מחפשת כעת עובדים חדשים. + עקבו אחרינו ב<a target="_blank" href="http://www.facebook.com/sefaria.org" >פייסבוק</a>  + כדי להשאר מעודכנים במשרות עתידיות. + </span> + </p> + </div> + ); +}; + + +const fetchJobsJSON = async () => { + const currentDateTime = new Date().toISOString(); + const query = ` + query { + jobPostings( + pagination: { limit: -1 } + filters: { + jobPostingStartDate: { lte: \"${currentDateTime}\" } + jobPostingEndDate: { gte: \"${currentDateTime}\" } + } + ) { + data { + id + attributes { + jobLink + jobDescription + jobDepartmentCategory } } } - `; - - try { - const response = await fetch(STRAPI_INSTANCE + "/graphql", { - method: "POST", - mode: "cors", - cache: "no-cache", - credentials: "omit", - headers: { - "Content-Type": "application/json", - }, - redirect: "follow", - referrerPolicy: "no-referrer", - body: JSON.stringify({ query }), - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.statusText}`); - } - const data = await response.json(); - return data; - } catch (error) { - throw error; } - }; + `; + + try { + const response = await fetch(STRAPI_INSTANCE + "/graphql", { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "omit", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify({ query }), + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + throw error; + } +}; + +const JobsPage = memo(() => { + const [groupedJobPostings, setGroupedJobPostings] = useState({}); + const [error, setError] = useState(null); useEffect(() => { const loadJobPostings = async () => { @@ -2822,7 +2814,17 @@ const JobsPage = memo(() => { }; }); - setJobPostings(jobsFromStrapi); + // Group the job postings by department + const groupedJobs = jobsFromStrapi.reduce((jobPostingsGroupedByDepartment, jobPosting) => { + const category = jobPosting.jobDepartmentCategory; + if (!jobPostingsGroupedByDepartment[category]) { + jobPostingsGroupedByDepartment[category] = []; + } + jobPostingsGroupedByDepartment[category].push(jobPosting); + return jobPostingsGroupedByDepartment; + }, {}); + + setGroupedJobPostings(groupedJobs); } catch (error) { console.error("Fetch error:", error); setError("Error: Sefaria's CMS cannot be reached"); @@ -2841,9 +2843,9 @@ const JobsPage = memo(() => { <h1>{error}</h1> ) : ( <> - <JobsPageHeader jobsAreAvailable={jobPostings?.length} /> - {jobPostings?.length ? ( - <JobPostings jobPostings={jobPostings} /> + <JobsPageHeader jobsAreAvailable={Object.keys(groupedJobPostings)?.length} /> + {Object.keys(groupedJobPostings)?.length ? ( + <GroupedJobPostings groupedJobPostings={groupedJobPostings} /> ) : ( <NoJobsNotice /> )} From 4ac841b795fb6d5a58046a8f0373d91716317b50 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Mon, 4 Dec 2023 10:45:44 +0200 Subject: [PATCH 185/260] chore: remove unnecessary imports --- reader/views.py | 4 ++-- static/js/Misc.jsx | 2 +- static/js/TopicEditor.jsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reader/views.py b/reader/views.py index 6846950a46..e2bab5ddf0 100644 --- a/reader/views.py +++ b/reader/views.py @@ -62,9 +62,9 @@ from sefaria.helper.topic import get_topic, get_all_topics, get_topics_for_ref, get_topics_for_book, \ get_bulk_topics, recommend_topics, get_top_topic, get_random_topic, \ get_random_topic_source, edit_topic_source, \ - update_order_of_topic_sources, delete_ref_topic_link, update_authors_place_and_time, add_image_to_topic + update_order_of_topic_sources, delete_ref_topic_link, update_authors_place_and_time from sefaria.helper.community_page import get_community_page_items -from sefaria.helper.file import get_resized_file, thumbnail_image_file +from sefaria.helper.file import get_resized_file from sefaria.image_generator import make_img_http_response import sefaria.tracker as tracker diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index de1c881ba1..4bdedf372c 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -15,7 +15,7 @@ import {ContentText} from "./ContentText"; import ReactTags from "react-tag-autocomplete"; import {AdminEditorButton, useEditToggle} from "./AdminEditor"; import {CategoryEditor, ReorderEditor} from "./CategoryEditor"; -import {refSort, TopicImage} from "./TopicPage"; +import {refSort} from "./TopicPage"; import {TopicEditor} from "./TopicEditor"; import {generateContentForModal, SignUpModalKind} from './sefaria/signupModalContent'; import {SourceEditor} from "./SourceEditor"; diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 28f49cc3ce..82d4d4f24a 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -1,5 +1,5 @@ import Sefaria from "./sefaria/sefaria"; -import {InterfaceText, requestWithCallBack, ProfilePic, PictureUploader} from "./Misc"; +import {InterfaceText, requestWithCallBack, PictureUploader} from "./Misc"; import $ from "./sefaria/sefariaJquery"; import {AdminEditor} from "./AdminEditor"; import {Reorder} from "./CategoryEditor"; From c8bc43398897d847459b1067cfb2d85429903225 Mon Sep 17 00:00:00 2001 From: Brendan Galloway <brendan@flanksource.com> Date: Mon, 4 Dec 2023 10:55:35 +0200 Subject: [PATCH 186/260] fix: yq format --- build/ci/createJobFromRollout.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ci/createJobFromRollout.sh b/build/ci/createJobFromRollout.sh index c56baf1d25..c78551aae0 100755 --- a/build/ci/createJobFromRollout.sh +++ b/build/ci/createJobFromRollout.sh @@ -23,7 +23,7 @@ EOF kubectl get rollout $DEPLOY_ENV-web -o yaml | yq '.spec.template.spec' > spec.yaml yq -i '.spec.template.spec += load("spec.yaml")' job.yaml -yq -i '.spec.template.spec.restartPolicy = Never' job.yaml +yq -i '.spec.template.spec.restartPolicy = "Never"' job.yaml yq -i '.spec.template.spec.containers[0].args = ["-c", "pip3 install pytest-django; pytest -v -m \"not deep and not failing\" ./sefaria; echo $? > /dev/stdout; exit 0;"]' job.yaml kubectl apply -f job.yaml From 0c17b44f43443249bd8696eccb2702f9e50e30c1 Mon Sep 17 00:00:00 2001 From: Brendan Galloway <brendan@flanksource.com> Date: Mon, 4 Dec 2023 13:16:59 +0200 Subject: [PATCH 187/260] fix: remove healthchecks from job pod --- build/ci/createJobFromRollout.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/ci/createJobFromRollout.sh b/build/ci/createJobFromRollout.sh index c78551aae0..0bb8fafee8 100755 --- a/build/ci/createJobFromRollout.sh +++ b/build/ci/createJobFromRollout.sh @@ -25,5 +25,8 @@ kubectl get rollout $DEPLOY_ENV-web -o yaml | yq '.spec.template.spec' > spec.ya yq -i '.spec.template.spec += load("spec.yaml")' job.yaml yq -i '.spec.template.spec.restartPolicy = "Never"' job.yaml yq -i '.spec.template.spec.containers[0].args = ["-c", "pip3 install pytest-django; pytest -v -m \"not deep and not failing\" ./sefaria; echo $? > /dev/stdout; exit 0;"]' job.yaml +yq -i 'del(.spec.template.spec.containers[0].startupProbe)' job.yaml +yq -i 'del(.spec.template.spec.containers[0].livenessProbe)' job.yaml +yq -i 'del(.spec.template.spec.containers[0].readinessProbe)' job.yaml kubectl apply -f job.yaml From 78dc3554b6246182e12a644afce8f02930ec1d04 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 4 Dec 2023 19:21:54 +0200 Subject: [PATCH 188/260] fix(translations): rerender on any change (including resize) for getting ellipsis right. --- static/js/VersionBlockHeader.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlockHeader.jsx index 6bcc015464..2b4207d3ce 100644 --- a/static/js/VersionBlockHeader.jsx +++ b/static/js/VersionBlockHeader.jsx @@ -48,7 +48,7 @@ function VersionBlockHeaderText({link, onClick, text, direction}) { const computedStyles = window.getComputedStyle(element); const maxHeight = parseInt(computedStyles.getPropertyValue('max-height'), 10); setTruncationOccurred(element.scrollHeight > maxHeight); - }, []); //[] for running in resize seems better than adding a listener + }); //no second param for running in resize seems better than adding a listener function onEllipsisClick() { setShouldAttemptTruncation(false); setTruncationOccurred(false); From baee75ffb559741a363f78ff05351b3ee06e671e Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 4 Dec 2023 19:25:54 +0200 Subject: [PATCH 189/260] feat(translations): no underline when hovering the content of translation. --- static/css/s2.css | 1 + 1 file changed, 1 insertion(+) diff --git a/static/css/s2.css b/static/css/s2.css index 8d0b3ee447..303a959f12 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3529,6 +3529,7 @@ display: none; .versionBlock .versionPreview { --line-height: 22px; line-height: var(--line-height); + text-decoration: none; } .versionBlock .versionPreview big { font-size: inherit; From 438e7c5703b7f1449747e0d0bb6681419f32fe91 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 4 Dec 2023 20:40:18 +0200 Subject: [PATCH 190/260] refactor(translations): use html details and summary rather than react useState. --- static/css/s2.css | 15 ++++--- static/js/VersionBlockWithPreview.jsx | 44 +++++++++---------- .../js/VersionBlockWithPreviewTitleLine.jsx | 9 ++-- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index 303a959f12..c7b98351fd 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3553,21 +3553,24 @@ display: none; font-size: 14px; line-height: 22px; color: var(--medium-grey); - margin-top: 10px; - margin-bottom: 10px; + margin-inline-start: 5px; } .version-with-preview-title-line .open-details { display: inline; margin-inline-end: 5px; font-style: italic; } -.version-with-preview-title-line .open-details.chevron-up::before { - content: url('/static/icons/little-chevron-up.svg'); +.versionBlock.with-preview summary { + margin-top: 10px; + margin-bottom: 10px; } -.version-with-preview-title-line .open-details.chevron-down::before { +.versionBlock.with-preview summary::marker { content: url('/static/icons/little-chevron-down.svg'); } -.version-with-preview-title-line .open-details::before { +[open] .versionBlock.with-preview summary::marker { + content: url('/static/icons/little-chevron-up.svg'); +} +details .open-details::before { margin-inline-end: 5px; } .version-with-preview-title-line .selectButton { diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlockWithPreview.jsx index 953ae4c95e..bf2d8b2395 100644 --- a/static/js/VersionBlockWithPreview.jsx +++ b/static/js/VersionBlockWithPreview.jsx @@ -7,7 +7,6 @@ import VersionPreviewMeta from "./VersionPreviewMeta"; import {OpenConnectionTabButton} from "./TextList"; function VersionBlockWithPreview({currentRef, version, currObjectVersions, openVersionInSidebar, openVersionInReader, isSelected, srefs, onRangeClick}) { - const [isInfoOpen, setIsInfoOpen] = useState(false); const opeInSidebar = versionTools.openVersionInSidebar.bind(null, currentRef, version, currObjectVersions, openVersionInSidebar); function openInTabCallback(sref) { onRangeClick(sref, false, {[version.language]: version.versionTitle}); @@ -21,27 +20,28 @@ function VersionBlockWithPreview({currentRef, version, currObjectVersions, openV link={versionTools.makeVersionLink(currentRef, version, currObjectVersions, false)} direction={version.direction || 'ltr'} /> - <VersionBlockWithPreviewTitleLine - version={version} - currentRef={currentRef} - currObjectVersions={currObjectVersions} - openVersionInReader={openVersionInReader} - isInfoOpen={isInfoOpen} - setIsInfoOpen={setIsInfoOpen} - isSelected={isSelected} - /> - {isInfoOpen && - <div className='version-block-with-preview-details'> - <VersionPreviewMeta - currentRef={currentRef} - version={version} - /> - <OpenConnectionTabButton - srefs={srefs} - openInTabCallback={openInTabCallback} - renderMode='versionPreview' - /> - </div>} + <details> + <summary> + <VersionBlockWithPreviewTitleLine + version={version} + currentRef={currentRef} + currObjectVersions={currObjectVersions} + openVersionInReader={openVersionInReader} + isSelected={isSelected} + /> + </summary> + <div className='version-block-with-preview-details'> + <VersionPreviewMeta + currentRef={currentRef} + version={version} + /> + <OpenConnectionTabButton + srefs={srefs} + openInTabCallback={openInTabCallback} + renderMode='versionPreview' + /> + </div> + </details> </div> ); } diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx index 09e1b56122..8c217310f7 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -4,7 +4,7 @@ import VersionBlockSelectButton from "./VersionBlockSelectButton"; import {versionTools} from './VersionBlock'; import Sefaria from "./sefaria/sefaria"; -function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersions, openVersionInReader, isInfoOpen, setIsInfoOpen, isSelected}) { +function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersions, openVersionInReader, isSelected}) { function makeShortVersionTitle() { let shortVersionTitle = version.shortVersionTitle || version.versionTitle; if (Sefaria.interfaceLang === "hebrew") { @@ -12,15 +12,14 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio } return shortVersionTitle; } - const chevronDirection = isInfoOpen ? 'up' : 'down'; const openVersionInMainPanel = versionTools.openVersionInMainPanel.bind(null, currentRef, version, currObjectVersions, 'select-button', null, openVersionInReader); const buttonText = isSelected ? 'Currently Selected' : 'Select'; return ( <div className='version-with-preview-title-line'> - <a className={`open-details chevron-${chevronDirection}`} onClick={() => setIsInfoOpen(!isInfoOpen)} href='#'> + <div className='open-details'> {makeShortVersionTitle()} - </a> + </div> <VersionBlockSelectButton isSelected={isSelected} openVersionInMainPanel={openVersionInMainPanel} @@ -35,8 +34,6 @@ VersionBlockWithPreviewTitleLine.prototypes = { version: PropTypes.object.isRequired, currentRef: PropTypes.string.isRequired, openVersionInReader: PropTypes.func.isRequired, - isInfoOpen: PropTypes.bool.isRequired, - setIsInfoOpen: PropTypes.func.isRequired, isSelected: PropTypes.bool.isRequired, }; export default VersionBlockWithPreviewTitleLine; From 08d78633c0f7b31e49d4d64500d33e872f63fe3a Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 4 Dec 2023 20:52:20 +0200 Subject: [PATCH 191/260] feat(translations): vertical space between blocks of 30px. --- static/css/s2.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index c7b98351fd..9658ec4e3a 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3562,6 +3562,8 @@ display: none; } .versionBlock.with-preview summary { margin-top: 10px; +} +[open] .versionBlock.with-preview summary { margin-bottom: 10px; } .versionBlock.with-preview summary::marker { @@ -3602,11 +3604,12 @@ details .open-details::before { line-height: 29px; max-inline-size: max-content; } -.versionsBox .versionBlock { - padding: 20px 0; -} .versionsBox .versionBlock:not(.with-preview) { border-top: solid 1px #CCC; + padding: 20px 0; +} +.versionsBox .versionBlock.with-preview { + padding: 15px 0; } .language-block .versionLanguage { border-bottom: solid 1px #CCC; From 4b042fdb1c0be4770b11c423be5d25c6ef46c8ad Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Tue, 5 Dec 2023 11:12:44 +0200 Subject: [PATCH 192/260] helm(search): fix typo --- .../templates/cronjob/reindex-elasticsearch.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml index 97ca61e379..640fd4c4ab 100644 --- a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml @@ -33,7 +33,7 @@ spec: memory: 7Gi env: - name: SEARCH_HOST - value: "{{ .Values.cronjobs.reindexElasticSearch.SEARCH_HOST_ES8 }}" + value: "{{ .Values.cronJobs.reindexElasticSearch.SEARCH_HOST_ES8 }}" - name: REDIS_HOST value: "redis-{{ .Values.deployEnv }}" - name: NODEJS_HOST From 38dc6c263f14d2c086a671fd175f94db210bbd8b Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Tue, 5 Dec 2023 11:33:05 +0200 Subject: [PATCH 193/260] chore: switch 200 to 150 caption maximum --- static/js/TopicEditor.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 82d4d4f24a..a42c690d82 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -93,12 +93,12 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { alert(Sefaria._("Title must be provided.")); return false; } - if (data.enImgCaption.length > 200) { - alert("English caption is too long. It should not be more than 200 characters"); + if (data.enImgCaption.length > 150) { + alert("English caption is too long. It should not be more than 150 characters"); return false; } - if (data.heImgCaption.length > 200) { - alert("Hebrew caption is too long. It should not be more than 200 characters") + if (data.heImgCaption.length > 150) { + alert("Hebrew caption is too long. It should not be more than 150 characters") return false; } if (sortedSubtopics.length > 0 && !isNew) { From 75fda5cfc5581155f4e2a81101b6b2fca9ceaa07 Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Tue, 5 Dec 2023 11:47:57 +0200 Subject: [PATCH 194/260] helm(search): fix typo --- .../templates/cronjob/reindex-elasticsearch-es6.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch-es6.yaml b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch-es6.yaml index 9d15f9fb38..9345a644fb 100644 --- a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch-es6.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch-es6.yaml @@ -33,7 +33,7 @@ spec: memory: 7Gi env: - name: SEARCH_HOST - value: "{{ .Values.cronjobs.reindexElasticSearch.SEARCH_HOST_ES6 }}" + value: "{{ .Values.cronJobs.reindexElasticSearch.SEARCH_HOST_ES6 }}" - name: REDIS_HOST value: "redis-{{ .Values.deployEnv }}" - name: NODEJS_HOST From 0df8aaaff549bf49c746faca43aa2247e2d197b0 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 5 Dec 2023 13:01:42 +0200 Subject: [PATCH 195/260] test: Remove expected failure on `test_check_first()` This tests appears to be passing and its theorized that recent code changes made it pass, when before we expected failure of this scenario. --- sefaria/model/tests/ref_catching_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sefaria/model/tests/ref_catching_test.py b/sefaria/model/tests/ref_catching_test.py index 900d8c7c49..970cd91996 100644 --- a/sefaria/model/tests/ref_catching_test.py +++ b/sefaria/model/tests/ref_catching_test.py @@ -97,6 +97,5 @@ def test_regex_string_he_in_parentheses_3(self): assert In('<p>[שיר השירים א ירושלמי כתובות (דף כח:) בשורות א]')\ .looking_for('שיר השירים').with_parenthesis().finds("Song of Songs 1") - @pytest.mark.xfail(reason="Linker doesn't know that it should look for either Mishnah or Talmud. This is as opposed to Ref instantiation where this ref would parse correctly because it would find the check_first field on the index.") def test_check_first(self): assert In('בבא מציעא פ"ד מ"ו, ועיין לעיל').looking_for('בבא מציעא').finds("Mishnah Bava Metzia 4:6") From 59bf1dd27a41f998aecb4ff28ac0b2594ad2ef07 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 5 Dec 2023 13:06:45 +0200 Subject: [PATCH 196/260] test: Refactor tests for mismatched actualLanguage saving The previous code was fixed to cascade the actual language in brackets in the version title to supercede any actualLanguage data set before. --- sefaria/model/tests/text_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sefaria/model/tests/text_test.py b/sefaria/model/tests/text_test.py index 6609c17354..8e3fa74243 100644 --- a/sefaria/model/tests/text_test.py +++ b/sefaria/model/tests/text_test.py @@ -768,7 +768,7 @@ def setup_class(cls): ) cls.versionWithoutTranslation.chapter = [['1'], ['2'], ["original text", "2nd"]] cls.versionWithoutTranslation.save() - cls.versionThatWillBreak = model.Version( + cls.versionWithLangCodeMismatch = model.Version( { "chapter": cls.myIndex.nodes.create_skeleton(), "versionTitle": "Version 1 TEST [ar]", @@ -781,7 +781,7 @@ def setup_class(cls): @classmethod def teardown_class(cls): - for c in [cls.myIndex, cls.versionWithTranslation, cls.versionWithoutTranslation, cls.versionThatWillBreak]: + for c in [cls.myIndex, cls.versionWithTranslation, cls.versionWithoutTranslation, cls.versionWithLangCodeMismatch]: try: c.delete() except Exception: @@ -793,6 +793,6 @@ def test_normalizes_actualLanguage_from_brackets(self): def test_normalizes_language_from_language(self): assert self.versionWithoutTranslation.actualLanguage == "he" - def test_fails_validation_when_language_mismatch(self): - with pytest.raises(InputError, match='Version actualLanguage does not match bracketed language'): - self.versionThatWillBreak.save() \ No newline at end of file + def test_save_when_language_mismatch(self): + self.versionWithLangCodeMismatch.save() + assert self.versionWithLangCodeMismatch.actualLanguage == "ar" \ No newline at end of file From 478ea6d9370c8c4169fb3a761a622712f8a47c16 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 5 Dec 2023 15:33:36 +0200 Subject: [PATCH 197/260] Revert "ci: Temporarily disable sandbox uninstall" This reverts commit da7f921bd85673055aebebd48a64ffcf45b4adc7. --- .github/workflows/continuous.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index e0a71c824a..4c9dfd788f 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -383,5 +383,5 @@ jobs: NAMESPACE: ${{ secrets.DEV_SANDBOX_NAMESPACE }} NAME: sandbox-${{ steps.get-sha.outputs.sha_short }} - name: Uninstall - run: echo "helm delete sandbox-${{ steps.get-sha.outputs.sha_short }} -n ${{ secrets.DEV_SANDBOX_NAMESPACE }} --debug --timeout 10m0s" + run: helm delete sandbox-${{ steps.get-sha.outputs.sha_short }} -n ${{ secrets.DEV_SANDBOX_NAMESPACE }} --debug --timeout 10m0s if: steps.get-helm.outputs.count > 0 From db1c693cd4406e1e4ebfd554c4e514d58bca9db8 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 5 Dec 2023 17:44:28 +0200 Subject: [PATCH 198/260] ci: Remove old and now deprecated pyTestPod.yaml It has been superceded by createJobFromRollout for the purpose of running pytests --- build/ci/pyTestPod.yaml | 103 ---------------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 build/ci/pyTestPod.yaml diff --git a/build/ci/pyTestPod.yaml b/build/ci/pyTestPod.yaml deleted file mode 100644 index d618a265e3..0000000000 --- a/build/ci/pyTestPod.yaml +++ /dev/null @@ -1,103 +0,0 @@ ---- -apiVersion: batch/v1 -kind: Job -metadata: - labels: - ci-run: "${GITHUB_RUN_ID}" - test-name: pytest - name: "${DEPLOY_ENV}-pytest-sandbox-${GITHUB_RUN_ID}" -spec: - backoffLimit: 2 # in waitForCIJob, we look for 2 fails before declaring failure. This could be made a variable. - template: - metadata: - labels: - ci-run: "${GITHUB_RUN_ID}" - test-name: pytest - spec: - restartPolicy: Never - containers: - - name: web - image: "${WEB_IMAGE_NAME}:${WEB_IMAGE_TAG}" - imagePullPolicy: Always - command: ["bash"] - args: ["-c", "pip3 install pytest-django; pytest -v -m \"not deep and not failing\" ./sefaria; echo $? > /dev/stdout; exit 0;"] - env: - # WEB_CONCURRENCY is used for determining the number of server workers - - name: GOOGLE_APPLICATION_CREDENTIALS - value: /app/logging-secret.json - - name: ENV_NAME - value: "${DEPLOY_ENV}" - - name: STACK_COMPONENT - value: web - - name: REDIS_HOST - value: "redis-${DEPLOY_ENV}" - - name: NODEJS_HOST - value: "node-${DEPLOY_ENV}" - - name: VARNISH_HOST - value: "varnish-${DEPLOY_ENV}" - envFrom: - - secretRef: - name: local-settings-secrets - optional: true - - configMapRef: - name: "local-settings-${DEPLOY_ENV}" - ports: - - containerPort: 80 - protocol: TCP - resources: - requests: - memory: "3Gi" - cpu: "500m" - limits: - memory: "3Gi" - cpu: "1000m" - volumeMounts: - - mountPath: /app/sefaria/local_settings.py - name: local-settings - subPath: local_settings.py - readOnly: true - - mountPath: /varnish-secret - name: varnish-secret - readOnly: true - - mountPath: /school-lookup-data - name: school-lookup-data - readOnly: true - - mountPath: /client-secret - name: client-secret - readOnly: true - - mountPath: /google-cloud-secret - name: backup-manager-secret - readOnly: true - - mountPath: /app/logging-secret.json - name: logging-secret - subPath: logging-secret.json - readOnly: true - - name: gunicorn-config - mountPath: /app/gunicorn.conf.py - subPath: gunicorn.conf.py - readOnly: true - volumes: - - name: local-settings - configMap: - name: "local-settings-file-${DEPLOY_ENV}" - items: - - key: local_settings.py - path: local_settings.py - - name: client-secret - secret: - secretName: google-client-secret - - name: backup-manager-secret # used to access google cloud - secret: - secretName: backup-manager - - name: logging-secret - secret: - secretName: logging-secret - - name: varnish-secret - secret: - secretName: varnish-secret-helm - - name: school-lookup-data - secret: - secretName: school-lookup-data - - name: gunicorn-config - configMap: - name: "gunicorn-config-${DEPLOY_ENV}" From 98b802df53f2e50fec7fcdc9cee1f594f71d4cb5 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 5 Dec 2023 17:44:37 +0200 Subject: [PATCH 199/260] Revert "chore(tests): remain tests db after done." This reverts commit e5f8a3d1ad69d88afe47045cd0c787fe06abc8c4. --- build/ci/sandbox-values.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build/ci/sandbox-values.yaml b/build/ci/sandbox-values.yaml index 40466aa962..12dcce6766 100644 --- a/build/ci/sandbox-values.yaml +++ b/build/ci/sandbox-values.yaml @@ -1,8 +1,6 @@ sandbox: "true" contentSandbox: "true" -restore: - cleanup: false -deployEnv: +deployEnv: previousServicesCount: "1" web: containerImage: From 4cddbaac2f823d742cc5a89710dcc6f36105af5c Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 5 Dec 2023 19:10:13 +0200 Subject: [PATCH 200/260] helm(feat): Add cronjob for Trend calculation --- build/ci/production-values.yaml | 2 + .../templates/cronjob/trends.yaml | 56 +++++++++++++++++++ helm-chart/sefaria-project/values.yaml | 2 + .../recurring-scheduled/recalculate-trends.py | 9 +++ 4 files changed, 69 insertions(+) create mode 100644 helm-chart/sefaria-project/templates/cronjob/trends.yaml create mode 100644 scripts/recurring-scheduled/recalculate-trends.py diff --git a/build/ci/production-values.yaml b/build/ci/production-values.yaml index bbf9b243d0..5f165ac2e0 100644 --- a/build/ci/production-values.yaml +++ b/build/ci/production-values.yaml @@ -185,6 +185,8 @@ cronJobs: enabled: true trello: enabled: true + trends: + enabled: false weeklyEmailNotifications: enabled: true secrets: diff --git a/helm-chart/sefaria-project/templates/cronjob/trends.yaml b/helm-chart/sefaria-project/templates/cronjob/trends.yaml new file mode 100644 index 0000000000..769f6c48e3 --- /dev/null +++ b/helm-chart/sefaria-project/templates/cronjob/trends.yaml @@ -0,0 +1,56 @@ +{{- if .Values.cronJobs.trends.enabled }} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ .Values.deployEnv }}-trends + labels: + {{- include "sefaria.labels" . | nindent 4 }} +spec: + schedule: "0 1 * * 6" + concurrencyPolicy: Forbid + jobTemplate: + spec: + backoffLimit: 1 + template: + spec: + volumes: + - name: local-settings + configMap: + name: local-settings-file-{{ .Values.deployEnv }} + items: + - key: local_settings.py + path: local_settings.py + containers: + - name: trends + image: "{{ .Values.web.containerImage.imageRegistry }}:{{ .Values.web.containerImage.tag }}" + env: + - name: REDIS_HOST + value: "redis-{{ .Values.deployEnv }}" + - name: NODEJS_HOST + value: "node-{{ .Values.deployEnv }}-{{ .Release.Revision }}" + - name: VARNISH_HOST + value: "varnish-{{ .Values.deployEnv }}-{{ .Release.Revision }}" + envFrom: + - secretRef: + name: {{ .Values.secrets.localSettings.ref }} + optional: true + - secretRef: + name: local-settings-secrets-{{ .Values.deployEnv }} + optional: true + - configMapRef: + name: local-settings-{{ .Values.deployEnv }} + volumeMounts: + - mountPath: /app/sefaria/local_settings.py + name: local-settings + subPath: local_settings.py + readOnly: true + command: ["bash"] + args: [ + "-c", + "/app/run /app/scripts/recurring-scheduled/recalculate-trends.py" + ] + restartPolicy: OnFailure + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 2 +{{- end }} diff --git a/helm-chart/sefaria-project/values.yaml b/helm-chart/sefaria-project/values.yaml index 72722c98b7..e67f71eb8f 100644 --- a/helm-chart/sefaria-project/values.yaml +++ b/helm-chart/sefaria-project/values.yaml @@ -405,6 +405,8 @@ cronJobs: enabled: false trello: enabled: false + trends: + enabled: false weeklyEmailNotifications: enabled: false diff --git a/scripts/recurring-scheduled/recalculate-trends.py b/scripts/recurring-scheduled/recalculate-trends.py new file mode 100644 index 0000000000..953840503d --- /dev/null +++ b/scripts/recurring-scheduled/recalculate-trends.py @@ -0,0 +1,9 @@ +import django +django.setup() +from sefaria.model import * + +trend.setAllTrends() + + + + From eda8bbc0adf978d74afd7dddb3c21f77a611e328 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 5 Dec 2023 19:15:39 +0200 Subject: [PATCH 201/260] chore: Rename directory and script name to conform to the rest of the codebase --- helm-chart/sefaria-project/templates/cronjob/trends.yaml | 2 +- .../recalculate-trends.py => scheduled/recalculate_trends.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename scripts/{recurring-scheduled/recalculate-trends.py => scheduled/recalculate_trends.py} (100%) diff --git a/helm-chart/sefaria-project/templates/cronjob/trends.yaml b/helm-chart/sefaria-project/templates/cronjob/trends.yaml index 769f6c48e3..47fc9c2b91 100644 --- a/helm-chart/sefaria-project/templates/cronjob/trends.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/trends.yaml @@ -48,7 +48,7 @@ spec: command: ["bash"] args: [ "-c", - "/app/run /app/scripts/recurring-scheduled/recalculate-trends.py" + "/app/run /app/scripts/scheduled/recalculate_trends.py" ] restartPolicy: OnFailure successfulJobsHistoryLimit: 1 diff --git a/scripts/recurring-scheduled/recalculate-trends.py b/scripts/scheduled/recalculate_trends.py similarity index 100% rename from scripts/recurring-scheduled/recalculate-trends.py rename to scripts/scheduled/recalculate_trends.py From b44595196193465f9712d96463c287fcd4147a27 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 5 Dec 2023 19:32:45 +0200 Subject: [PATCH 202/260] chore: move all cronjob related scripts to their own directory --- .../templates/cronjob/daily-email-notifications.yaml | 2 +- .../sefaria-project/templates/cronjob/index-from-queue.yaml | 2 +- helm-chart/sefaria-project/templates/cronjob/metrics.yaml | 2 +- .../sefaria-project/templates/cronjob/nation-builder-sync.yaml | 2 +- .../templates/cronjob/rambi-webpages-weekly.yaml | 2 +- .../templates/cronjob/regenerate-long-cached-data.yaml | 2 +- .../templates/cronjob/reindex-elasticsearch-es6.yaml | 2 +- .../templates/cronjob/reindex-elasticsearch.yaml | 3 ++- helm-chart/sefaria-project/templates/cronjob/sitemaps.yaml | 2 +- .../sefaria-project/templates/cronjob/topics-indexing.yaml | 2 +- helm-chart/sefaria-project/templates/cronjob/trello.yaml | 2 +- .../templates/cronjob/weekly-email-notifications.yaml | 2 +- scripts/{ => scheduled}/generate_sitemaps.py | 0 scripts/{ => scheduled}/index_from_queue.py | 0 scripts/{ => scheduled}/metrics.py | 0 scripts/{ => scheduled}/nation_builder_sync.py | 0 scripts/{ => scheduled}/parse_rambi_webpages.py | 0 scripts/{ => scheduled}/recalculate_secondary_topic_data.py | 0 scripts/{ => scheduled}/regenerate_long_cached_data.py | 0 scripts/{ => scheduled}/reindex_elasticsearch_cronjob.py | 0 scripts/{ => scheduled}/reindex_elasticsearch_cronjob_ES6.py | 0 scripts/{ => scheduled}/send_email_notifications.py | 0 scripts/{ => scheduled}/webpages_cronjob.py | 0 23 files changed, 13 insertions(+), 12 deletions(-) rename scripts/{ => scheduled}/generate_sitemaps.py (100%) rename scripts/{ => scheduled}/index_from_queue.py (100%) rename scripts/{ => scheduled}/metrics.py (100%) rename scripts/{ => scheduled}/nation_builder_sync.py (100%) rename scripts/{ => scheduled}/parse_rambi_webpages.py (100%) rename scripts/{ => scheduled}/recalculate_secondary_topic_data.py (100%) rename scripts/{ => scheduled}/regenerate_long_cached_data.py (100%) rename scripts/{ => scheduled}/reindex_elasticsearch_cronjob.py (100%) rename scripts/{ => scheduled}/reindex_elasticsearch_cronjob_ES6.py (100%) rename scripts/{ => scheduled}/send_email_notifications.py (100%) rename scripts/{ => scheduled}/webpages_cronjob.py (100%) diff --git a/helm-chart/sefaria-project/templates/cronjob/daily-email-notifications.yaml b/helm-chart/sefaria-project/templates/cronjob/daily-email-notifications.yaml index d7a7016c4f..ed4d06abcb 100644 --- a/helm-chart/sefaria-project/templates/cronjob/daily-email-notifications.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/daily-email-notifications.yaml @@ -40,7 +40,7 @@ spec: command: ["bash"] args: [ "-c", - "/app/run /app/scripts/send_email_notifications.py daily" + "/app/run /app/scripts/scheduled/send_email_notifications.py daily" ] restartPolicy: Never volumes: diff --git a/helm-chart/sefaria-project/templates/cronjob/index-from-queue.yaml b/helm-chart/sefaria-project/templates/cronjob/index-from-queue.yaml index 227bf78520..4d147093fc 100644 --- a/helm-chart/sefaria-project/templates/cronjob/index-from-queue.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/index-from-queue.yaml @@ -51,7 +51,7 @@ spec: command: ["bash"] args: [ "-c", - "/app/run /app/scripts/index_from_queue.py" + "/app/run /app/scripts/scheduled/index_from_queue.py" ] resources: limits: diff --git a/helm-chart/sefaria-project/templates/cronjob/metrics.yaml b/helm-chart/sefaria-project/templates/cronjob/metrics.yaml index 9b30aee20e..10e19f3103 100644 --- a/helm-chart/sefaria-project/templates/cronjob/metrics.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/metrics.yaml @@ -40,7 +40,7 @@ spec: command: ["bash"] args: [ "-c", - "/app/run /app/scripts/metrics.py" + "/app/run /app/scripts/scheduled/metrics.py" ] restartPolicy: OnFailure volumes: diff --git a/helm-chart/sefaria-project/templates/cronjob/nation-builder-sync.yaml b/helm-chart/sefaria-project/templates/cronjob/nation-builder-sync.yaml index a58551879a..c119ab6d36 100644 --- a/helm-chart/sefaria-project/templates/cronjob/nation-builder-sync.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/nation-builder-sync.yaml @@ -47,7 +47,7 @@ spec: command: ["bash"] args: [ "-c", - "/app/run /app/scripts/nation_builder_sync.py --sustainers-only" + "/app/run /app/scripts/scheduled/nation_builder_sync.py --sustainers-only" ] restartPolicy: OnFailure successfulJobsHistoryLimit: 1 diff --git a/helm-chart/sefaria-project/templates/cronjob/rambi-webpages-weekly.yaml b/helm-chart/sefaria-project/templates/cronjob/rambi-webpages-weekly.yaml index 31038a4c07..a3f67ef4cb 100644 --- a/helm-chart/sefaria-project/templates/cronjob/rambi-webpages-weekly.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/rambi-webpages-weekly.yaml @@ -51,7 +51,7 @@ spec: command: ["bash"] args: [ "-c", - "pip install pymarc==4.2.2 && /app/run /app/scripts/parse_rambi_webpages.py" + "pip install pymarc==4.2.2 && /app/run /app/scripts/scheduled/parse_rambi_webpages.py" ] resources: requests: diff --git a/helm-chart/sefaria-project/templates/cronjob/regenerate-long-cached-data.yaml b/helm-chart/sefaria-project/templates/cronjob/regenerate-long-cached-data.yaml index ae935a86bc..262d9a3bfe 100644 --- a/helm-chart/sefaria-project/templates/cronjob/regenerate-long-cached-data.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/regenerate-long-cached-data.yaml @@ -40,7 +40,7 @@ spec: command: ["bash"] args: [ "-c", - "/app/run /app/scripts/regenerate_long_cached_data.py --all" + "/app/run /app/scripts/scheduled/regenerate_long_cached_data.py --all" ] restartPolicy: OnFailure volumes: diff --git a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch-es6.yaml b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch-es6.yaml index 9345a644fb..c35cb324cc 100644 --- a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch-es6.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch-es6.yaml @@ -62,7 +62,7 @@ spec: command: ["bash"] args: [ "-c", - "mkdir -p /log && touch /log/sefaria_book_errors.log && pip install numpy && /app/run /app/scripts/reindex_elasticsearch_cronjob_ES6.py" + "mkdir -p /log && touch /log/sefaria_book_errors.log && pip install numpy && /app/run /app/scripts/scheduled/reindex_elasticsearch_cronjob_ES6.py" ] restartPolicy: Never volumes: diff --git a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml index 640fd4c4ab..7aab052a47 100644 --- a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml @@ -64,7 +64,8 @@ spec: command: ["bash"] args: [ "-c", - "mkdir -p /log && touch /log/sefaria_book_errors.log && pip install numpy elasticsearch==8.8.2 git+https://github.com/Sefaria/elasticsearch-dsl-py@v8.0.0#egg=elasticsearch-dsl && /app/run /app/scripts/reindex_elasticsearch_cronjob.py" + "mkdir -p /log && touch /log/sefaria_book_errors.log && pip install numpy elasticsearch==8.8.2 git+https://github.com/Sefaria/elasticsearch-dsl-py@v8.0 + .0#egg=elasticsearch-dsl && /app/run /app/scripts/scheduled/reindex_elasticsearch_cronjob.py" ] restartPolicy: Never volumes: diff --git a/helm-chart/sefaria-project/templates/cronjob/sitemaps.yaml b/helm-chart/sefaria-project/templates/cronjob/sitemaps.yaml index c279ec87ad..084a5f185d 100644 --- a/helm-chart/sefaria-project/templates/cronjob/sitemaps.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/sitemaps.yaml @@ -50,7 +50,7 @@ spec: subPath: local_settings.py readOnly: true command: ["bash"] - args: ["-c", "/app/run /app/scripts/generate_sitemaps.py -o /storage/"] + args: ["-c", "/app/run /app/scripts/scheduled/generate_sitemaps.py -o /storage/"] containers: - name: file-uploader image: google/cloud-sdk diff --git a/helm-chart/sefaria-project/templates/cronjob/topics-indexing.yaml b/helm-chart/sefaria-project/templates/cronjob/topics-indexing.yaml index b905f8b3e6..049f3aa7b1 100644 --- a/helm-chart/sefaria-project/templates/cronjob/topics-indexing.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/topics-indexing.yaml @@ -18,7 +18,7 @@ spec: containers: - name: topics-indexer image: "{{ .Values.web.containerImage.imageRegistry }}:{{ .Values.web.containerImage.tag }}" - args: [ "yes | pip3 install numpy && touch /log/sefaria_book_errors.log && python3 /app/scripts/recalculate_secondary_topic_data.py" ] + args: [ "yes | pip3 install numpy && touch /log/sefaria_book_errors.log && python3 /app/scripts/scheduled/recalculate_secondary_topic_data.py" ] env: - name: REDIS_HOST value: "redis-{{ .Values.deployEnv }}" diff --git a/helm-chart/sefaria-project/templates/cronjob/trello.yaml b/helm-chart/sefaria-project/templates/cronjob/trello.yaml index 220a4821ce..5b3f6c9ad9 100644 --- a/helm-chart/sefaria-project/templates/cronjob/trello.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/trello.yaml @@ -58,7 +58,7 @@ spec: command: ["bash"] args: [ "-c", - "/app/run /app/scripts/webpages_cronjob.py" + "/app/run /app/scripts/scheduled/webpages_cronjob.py" ] resources: limits: diff --git a/helm-chart/sefaria-project/templates/cronjob/weekly-email-notifications.yaml b/helm-chart/sefaria-project/templates/cronjob/weekly-email-notifications.yaml index 8cc6bcf853..eb407fbd49 100644 --- a/helm-chart/sefaria-project/templates/cronjob/weekly-email-notifications.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/weekly-email-notifications.yaml @@ -40,7 +40,7 @@ spec: command: ["bash"] args: [ "-c", - "/app/run /app/scripts/send_email_notifications.py weekly" + "/app/run /app/scripts/scheduled/send_email_notifications.py weekly" ] restartPolicy: Never volumes: diff --git a/scripts/generate_sitemaps.py b/scripts/scheduled/generate_sitemaps.py similarity index 100% rename from scripts/generate_sitemaps.py rename to scripts/scheduled/generate_sitemaps.py diff --git a/scripts/index_from_queue.py b/scripts/scheduled/index_from_queue.py similarity index 100% rename from scripts/index_from_queue.py rename to scripts/scheduled/index_from_queue.py diff --git a/scripts/metrics.py b/scripts/scheduled/metrics.py similarity index 100% rename from scripts/metrics.py rename to scripts/scheduled/metrics.py diff --git a/scripts/nation_builder_sync.py b/scripts/scheduled/nation_builder_sync.py similarity index 100% rename from scripts/nation_builder_sync.py rename to scripts/scheduled/nation_builder_sync.py diff --git a/scripts/parse_rambi_webpages.py b/scripts/scheduled/parse_rambi_webpages.py similarity index 100% rename from scripts/parse_rambi_webpages.py rename to scripts/scheduled/parse_rambi_webpages.py diff --git a/scripts/recalculate_secondary_topic_data.py b/scripts/scheduled/recalculate_secondary_topic_data.py similarity index 100% rename from scripts/recalculate_secondary_topic_data.py rename to scripts/scheduled/recalculate_secondary_topic_data.py diff --git a/scripts/regenerate_long_cached_data.py b/scripts/scheduled/regenerate_long_cached_data.py similarity index 100% rename from scripts/regenerate_long_cached_data.py rename to scripts/scheduled/regenerate_long_cached_data.py diff --git a/scripts/reindex_elasticsearch_cronjob.py b/scripts/scheduled/reindex_elasticsearch_cronjob.py similarity index 100% rename from scripts/reindex_elasticsearch_cronjob.py rename to scripts/scheduled/reindex_elasticsearch_cronjob.py diff --git a/scripts/reindex_elasticsearch_cronjob_ES6.py b/scripts/scheduled/reindex_elasticsearch_cronjob_ES6.py similarity index 100% rename from scripts/reindex_elasticsearch_cronjob_ES6.py rename to scripts/scheduled/reindex_elasticsearch_cronjob_ES6.py diff --git a/scripts/send_email_notifications.py b/scripts/scheduled/send_email_notifications.py similarity index 100% rename from scripts/send_email_notifications.py rename to scripts/scheduled/send_email_notifications.py diff --git a/scripts/webpages_cronjob.py b/scripts/scheduled/webpages_cronjob.py similarity index 100% rename from scripts/webpages_cronjob.py rename to scripts/scheduled/webpages_cronjob.py From 93fea2a02063ab0476807346b0d43708b1291c61 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 5 Dec 2023 20:46:16 +0200 Subject: [PATCH 203/260] fix: Actually enable new cronjob on prod --- build/ci/production-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ci/production-values.yaml b/build/ci/production-values.yaml index 5f165ac2e0..386c23eea9 100644 --- a/build/ci/production-values.yaml +++ b/build/ci/production-values.yaml @@ -186,7 +186,7 @@ cronJobs: trello: enabled: true trends: - enabled: false + enabled: true weeklyEmailNotifications: enabled: true secrets: From f386c210a5bee409fe379065f4632d592a1dd8de Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Wed, 6 Dec 2023 13:43:26 -0500 Subject: [PATCH 204/260] Reorganize functions into proper scope --- static/js/StaticPages.jsx | 196 +++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index adf5e016ee..09afb6f0c9 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2743,116 +2743,116 @@ const BoardMembers = ({ boardMembers }) => { ); }; -const fetchTeamMembersJSON = async () => { - const query = ` -query { - teamMembers(pagination: { limit: -1 }) { - data { - id - attributes { - teamName - teamTitle - isTeamBoardMember - teamMemberImage { - data { - attributes { - url - } - } - } - localizations { - data { - attributes { - locale - teamName - teamTitle - } - } - } - } - } - } -} -`; - try { - const response = await fetch(STRAPI_INSTANCE + "/graphql", { - method: "POST", - mode: "cors", - cache: "no-cache", - credentials: "omit", - headers: { - "Content-Type": "application/json", - }, - redirect: "follow", - referrerPolicy: "no-referrer", - body: JSON.stringify({ query }), - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.statusText}`); - } - const data = await response.json(); - return data; - } catch (error) { - throw error; - } -}; - const TeamMembersPage = memo(() => { const [ordinaryTeamMembers, setOrdinaryTeamMembers] = useState([]); const [teamBoardMembers, setTeamBoardMembers] = useState([]); const [error, setError] = useState(null); - useEffect(() => { - const loadTeamMembers = async () => { - if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { - try { - const teamMembersData = await fetchTeamMembersJSON(); - - const teamMembersFromStrapi = - teamMembersData.data.teamMembers.data.map( - (teamMember) => { - const heLocalization = - teamMember.attributes.localizations.data[0]; - - return { - id: teamMember.id, - isTeamBoardMember: - teamMember.attributes.isTeamBoardMember, - teamMemberImage: - teamMember.attributes.teamMemberImage - ?.data?.attributes?.url, - teamMemberDetails: { - teamName: { - en: teamMember.attributes.teamName, - he: heLocalization.attributes - .teamName, - }, - teamTitle: { - en: teamMember.attributes.teamTitle, - he: heLocalization.attributes - .teamTitle, - }, - }, - }; + const fetchTeamMembersJSON = async () => { + const query = ` + query { + teamMembers(pagination: { limit: -1 }) { + data { + id + attributes { + teamName + teamTitle + isTeamBoardMember + teamMemberImage { + data { + attributes { + url + } + } } - ); + localizations { + data { + attributes { + locale + teamName + teamTitle + } + } + } + } + } + } + } + `; + try { + const response = await fetch(STRAPI_INSTANCE + "/graphql", { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "omit", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify({ query }), + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + throw error; + } + }; - const [ordinaryMembers, boardMembers] = partition( - teamMembersFromStrapi, - (teamMember) => !teamMember.isTeamBoardMember + const loadTeamMembers = async () => { + if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { + try { + const teamMembersData = await fetchTeamMembersJSON(); + + const teamMembersFromStrapi = + teamMembersData.data.teamMembers.data.map( + (teamMember) => { + const heLocalization = + teamMember.attributes.localizations.data[0]; + + return { + id: teamMember.id, + isTeamBoardMember: + teamMember.attributes.isTeamBoardMember, + teamMemberImage: + teamMember.attributes.teamMemberImage + ?.data?.attributes?.url, + teamMemberDetails: { + teamName: { + en: teamMember.attributes.teamName, + he: heLocalization.attributes + .teamName, + }, + teamTitle: { + en: teamMember.attributes.teamTitle, + he: heLocalization.attributes + .teamTitle, + }, + }, + }; + } ); - setOrdinaryTeamMembers(ordinaryMembers); - setTeamBoardMembers(boardMembers); - } catch (error) { - console.error("Fetch error:", error); - setError("Error: Sefaria's CMS cannot be reached"); - } - } else { + const [ordinaryMembers, boardMembers] = partition( + teamMembersFromStrapi, + (teamMember) => !teamMember.isTeamBoardMember + ); + + setOrdinaryTeamMembers(ordinaryMembers); + setTeamBoardMembers(boardMembers); + } catch (error) { + console.error("Fetch error:", error); setError("Error: Sefaria's CMS cannot be reached"); } - }; + } else { + setError("Error: Sefaria's CMS cannot be reached"); + } + }; + useEffect(() => { loadTeamMembers(); }, []); From d37b5a107cd74e588c38bd70df2717fa9c2a4182 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Wed, 6 Dec 2023 14:13:48 -0500 Subject: [PATCH 205/260] Reorganize functions into proper scope --- static/js/StaticPages.jsx | 155 +++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 77 deletions(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 7ce3a00a69..8ae588a57e 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2747,93 +2747,94 @@ const NoJobsNotice = () => { }; -const fetchJobsJSON = async () => { - const currentDateTime = new Date().toISOString(); - const query = ` - query { - jobPostings( - pagination: { limit: -1 } - filters: { - jobPostingStartDate: { lte: \"${currentDateTime}\" } - jobPostingEndDate: { gte: \"${currentDateTime}\" } - } - ) { - data { - id - attributes { - jobLink - jobDescription - jobDepartmentCategory - } - } - } - } - `; - - try { - const response = await fetch(STRAPI_INSTANCE + "/graphql", { - method: "POST", - mode: "cors", - cache: "no-cache", - credentials: "omit", - headers: { - "Content-Type": "application/json", - }, - redirect: "follow", - referrerPolicy: "no-referrer", - body: JSON.stringify({ query }), - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.statusText}`); - } - const data = await response.json(); - return data; - } catch (error) { - throw error; - } -}; const JobsPage = memo(() => { const [groupedJobPostings, setGroupedJobPostings] = useState({}); const [error, setError] = useState(null); - useEffect(() => { - const loadJobPostings = async () => { - if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { - try { - const jobsData = await fetchJobsJSON(); - - const jobsFromStrapi = jobsData.data?.jobPostings?.data?.map((jobPosting) => { - return { - id: jobPosting.id, - jobLink: jobPosting.attributes.jobLink, - jobDescription: jobPosting.attributes.jobDescription, - jobDepartmentCategory: jobPosting.attributes.jobDepartmentCategory - .split("_") - .join(" "), - }; - }); - - // Group the job postings by department - const groupedJobs = jobsFromStrapi.reduce((jobPostingsGroupedByDepartment, jobPosting) => { - const category = jobPosting.jobDepartmentCategory; - if (!jobPostingsGroupedByDepartment[category]) { - jobPostingsGroupedByDepartment[category] = []; + const fetchJobsJSON = async () => { + const currentDateTime = new Date().toISOString(); + const query = ` + query { + jobPostings( + pagination: { limit: -1 } + filters: { + jobPostingStartDate: { lte: \"${currentDateTime}\" } + jobPostingEndDate: { gte: \"${currentDateTime}\" } + } + ) { + data { + id + attributes { + jobLink + jobDescription + jobDepartmentCategory } - jobPostingsGroupedByDepartment[category].push(jobPosting); - return jobPostingsGroupedByDepartment; - }, {}); - - setGroupedJobPostings(groupedJobs); - } catch (error) { - console.error("Fetch error:", error); - setError("Error: Sefaria's CMS cannot be reached"); + } } - } else { + } + `; + + try { + const response = await fetch(STRAPI_INSTANCE + "/graphql", { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "omit", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + referrerPolicy: "no-referrer", + body: JSON.stringify({ query }), + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.statusText}`); + } + const data = await response.json(); + return data; + } catch (error) { + throw error; + } + }; + + const loadJobPostings = async () => { + if (typeof STRAPI_INSTANCE !== "undefined" && STRAPI_INSTANCE) { + try { + const jobsData = await fetchJobsJSON(); + + const jobsFromStrapi = jobsData.data?.jobPostings?.data?.map((jobPosting) => { + return { + id: jobPosting.id, + jobLink: jobPosting.attributes.jobLink, + jobDescription: jobPosting.attributes.jobDescription, + jobDepartmentCategory: jobPosting.attributes.jobDepartmentCategory + .split("_") + .join(" "), + }; + }); + + // Group the job postings by department + const groupedJobs = jobsFromStrapi.reduce((jobPostingsGroupedByDepartment, jobPosting) => { + const category = jobPosting.jobDepartmentCategory; + if (!jobPostingsGroupedByDepartment[category]) { + jobPostingsGroupedByDepartment[category] = []; + } + jobPostingsGroupedByDepartment[category].push(jobPosting); + return jobPostingsGroupedByDepartment; + }, {}); + + setGroupedJobPostings(groupedJobs); + } catch (error) { + console.error("Fetch error:", error); setError("Error: Sefaria's CMS cannot be reached"); } - }; + } else { + setError("Error: Sefaria's CMS cannot be reached"); + } + }; + useEffect(() => { loadJobPostings(); }, []); From cae792bfae7990a0602d19743bcd7d83a156cc0b Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Thu, 7 Dec 2023 14:31:58 +0200 Subject: [PATCH 206/260] fix(Topic Editor): save topic upon upload/remove image --- reader/views.py | 45 +++++++++++++++++---------------------- sefaria/urls.py | 3 +-- static/js/Misc.jsx | 8 +++---- static/js/TopicEditor.jsx | 6 +++--- 4 files changed, 28 insertions(+), 34 deletions(-) diff --git a/reader/views.py b/reader/views.py index e2bab5ddf0..6481684eee 100644 --- a/reader/views.py +++ b/reader/views.py @@ -3561,41 +3561,36 @@ def profile_follow_api(request, ftype, slug): return jsonResponse(response) return jsonResponse({"error": "Unsupported HTTP method."}) - -@catch_error_as_json -def topic_delete_photo(request, file): - if not request.user.is_authenticated: - return jsonResponse({"error": _("You must be logged in to update a topic photo.")}) - bucket_name = GoogleStorageManager.TOPICS_BUCKET - if file: - file = f"topics/{file.split('/')[-1]}" - if request.method == "DELETE": - if file is None: - return jsonResponse({"error": "You cannot remove an image as you haven't selected one yet."}) - GoogleStorageManager.delete_filename(file, bucket_name) - return jsonResponse({"success": "You have successfully removed the image."}) - -@catch_error_as_json -def topic_upload_photo(request): +@staff_member_required +def topic_upload_photo(request, topic): from io import BytesIO import uuid import base64 - if not request.user.is_authenticated: - return jsonResponse({"error": _("You must be logged in to update a topic photo.")}) - bucket_name = GoogleStorageManager.TOPICS_BUCKET - file = request.POST.get('file') - old_filename = request.POST.get('old_filename') # delete file from google storage if there is one there - if old_filename: - old_filename = f"topics/{old_filename.split('/')[-1]}" if request.method == "DELETE": + old_filename = request.GET.get("old_filename") if old_filename is None: return jsonResponse({"error": "You cannot remove an image as you haven't selected one yet."}) - GoogleStorageManager.delete_filename(old_filename, bucket_name) + old_filename = f"topics/{old_filename.split('/')[-1]}" + GoogleStorageManager.delete_filename(old_filename, GoogleStorageManager.TOPICS_BUCKET) + topic = Topic.init(topic) + if hasattr(topic, "image"): + del topic.image + topic.save() return jsonResponse({"success": "You have successfully removed the image."}) elif request.method == "POST": + file = request.POST.get('file') + old_filename = request.POST.get('old_filename') # delete file from google storage if there is one there + if old_filename: + old_filename = f"topics/{old_filename.split('/')[-1]}" img_file_in_mem = BytesIO(base64.b64decode(file)) img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"topics/{request.user.id}-{uuid.uuid1()}.gif", - bucket_name, old_filename=old_filename) + GoogleStorageManager.TOPICS_BUCKET, old_filename=old_filename) + topic = Topic.init(topic) + if not hasattr(topic, "image"): + topic.image = {"image_uri": img_url, "image_caption": {"en": "", "he": ""}} + else: + topic.image["image_uri"] = img_url + topic.save() return jsonResponse({"url": img_url}) return jsonResponse({"error": "Unsupported HTTP method."}) diff --git a/sefaria/urls.py b/sefaria/urls.py index c1ae9404f9..8e7fbd0f94 100644 --- a/sefaria/urls.py +++ b/sefaria/urls.py @@ -103,8 +103,7 @@ url(r'^topics/b/(?P<topic>.+)$', reader_views.topic_page_b), url(r'^topics/(?P<topic>.+)$', reader_views.topic_page), url(r'^api/topic/completion/(?P<topic>.+)', reader_views.topic_completion_api), - url(r'^api/topics/upload-image$', reader_views.topic_upload_photo), - url(r'^api/topics/delete-image/(?P<file>.+)$', reader_views.topic_delete_photo), + url(r'^api/topics/images/(?P<topic>.+)$', reader_views.topic_upload_photo) ] diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 4bdedf372c..168caf1740 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1577,7 +1577,7 @@ FollowButton.propTypes = { }; -const PictureUploader = ({callback, old_filename, caption}) => { +const TopicPictureUploader = ({slug, callback, old_filename, caption}) => { /* `old_filename` is passed to API so that if it exists, it is deleted */ @@ -1590,7 +1590,7 @@ const PictureUploader = ({callback, old_filename, caption}) => { formData.append('old_filename', old_filename); } $.ajax({ - url: Sefaria.apiHost + "/api/topics/upload-image", + url: `${Sefaria.apiHost}/api/topics/images/${slug}`, type, data: formData, contentType: false, @@ -1630,7 +1630,7 @@ const PictureUploader = ({callback, old_filename, caption}) => { } const deleteImage = () => { const old_filename_wout_url = old_filename.split("/").slice(-1); - const url = `${Sefaria.apiHost}/api/topics/delete-image/${old_filename_wout_url}`; + const url = `${Sefaria.apiHost}/api/topics/images/${slug}?old_filename=${old_filename_wout_url}`; requestWithCallBack({url, type: "DELETE", redirect: () => alert("Deleted image.")}); callback(""); fileInput.current.value = ""; @@ -3339,6 +3339,6 @@ export { TitleVariants, requestWithCallBack, OnInView, - PictureUploader, + TopicPictureUploader, ImageWithCaption }; diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index a42c690d82..0c36f2489c 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -1,5 +1,5 @@ import Sefaria from "./sefaria/sefaria"; -import {InterfaceText, requestWithCallBack, PictureUploader} from "./Misc"; +import {InterfaceText, requestWithCallBack, TopicPictureUploader} from "./Misc"; import $ from "./sefaria/sefariaJquery"; import {AdminEditor} from "./AdminEditor"; import {Reorder} from "./CategoryEditor"; @@ -205,8 +205,8 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { items.push("Hebrew Caption"); return <AdminEditor title="Topic Editor" close={closeTopicEditor} catMenu={catMenu} data={data} savingStatus={savingStatus} validate={validate} deleteObj={deleteObj} updateData={updateData} isNew={isNew} items={items} - pictureUploader={<PictureUploader callback={handlePictureChange} old_filename={data.image_uri} - caption={{en: data.enImgCaption, he: data.heImgCaption}}/>} + pictureUploader={<TopicPictureUploader slug={data.origSlug} callback={handlePictureChange} old_filename={data.image_uri} + caption={{en: data.enImgCaption, he: data.heImgCaption}}/>} extras={ [isNew ? null : <Reorder subcategoriesAndBooks={sortedSubtopics} From fa4c5f30a67f3670435b9d09d8cf21e5a8ade84d Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Wed, 13 Dec 2023 11:05:43 +0200 Subject: [PATCH 207/260] chore: ajax to JS --- sefaria/helper/topic.py | 1 + static/js/Misc.jsx | 41 ++++++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/sefaria/helper/topic.py b/sefaria/helper/topic.py index d9c3bcd41b..d45a925030 100644 --- a/sefaria/helper/topic.py +++ b/sefaria/helper/topic.py @@ -1110,6 +1110,7 @@ def update_topic(topic, **kwargs): if image_dict["image_uri"] != "": topic.image = kwargs["image"] elif hasattr(topic, 'image'): + # we don't want captions without image_uris, so if the image_uri is blank, get rid of the caption too del topic.image topic.save() diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 168caf1740..4d7205fc74 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -1589,25 +1589,28 @@ const TopicPictureUploader = ({slug, callback, old_filename, caption}) => { if (old_filename !== "") { formData.append('old_filename', old_filename); } - $.ajax({ - url: `${Sefaria.apiHost}/api/topics/images/${slug}`, - type, - data: formData, - contentType: false, - processData: false, - success: function(data) { - if (data.error) { - alert(data.error); - } else { - if (data.url) { - callback(data.url); - } - }}, - error: function(e) { - alert(e); - } - }); - } + const request = new Request( + `${Sefaria.apiHost}/api/topics/images/${slug}`, + {headers: {'X-CSRFToken': Cookies.get('csrftoken')}} + ); + fetch(request, { + method: 'POST', + mode: 'same-origin', + credentials: 'same-origin', + body: formData + }).then(response => { + if (!response.ok) { + response.text().then(resp_text=> { + alert(resp_text); + }) + }else{ + response.json().then(resp_json=>{ + callback(resp_json.url); + }); + } + }).catch(error => { + alert(error); + })}; const onFileSelect = (e) => { const file = fileInput.current.files[0]; if (file == null) From 2929c046d188e54b9aaca42a44a73eccc214096c Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Wed, 13 Dec 2023 12:08:55 +0200 Subject: [PATCH 208/260] chore: add topics bucket to contextus site settings --- sites/s4d/site_settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sites/s4d/site_settings.py b/sites/s4d/site_settings.py index 481a278993..96df0b4bd6 100644 --- a/sites/s4d/site_settings.py +++ b/sites/s4d/site_settings.py @@ -14,5 +14,6 @@ "LIBRARY_MESSAGE": "This site is a proof of concept for bringing <a href='https://www.sefaria.org' target='_blank'>Sefaria</a>'s approach to interlinking Jewish texts to the United States Constitution and related writings. It contains an initial set of example texts, for which we are seeking a partner organization to build into a complete library. This library is not interconnected with Sefaria's library of Jewish texts. <a target='_blank' href='https://docs.google.com/document/d/e/2PACX-1vREoqWlYLC68jScm2yBO2cZY1SbLOjj67Tu59VdVVYf1lbnNIxiKG07GSNKlg6p77kIpauiqPRZsSsk/pub'>Learn More ›</a>", "COLLECTIONS_BUCKET": "jmc-collection-images", "PROFILES_BUCKET": 'jmc-profile-pictures', - "UGC_BUCKET": 'jmc-sheet-user-uploaded-media' + "UGC_BUCKET": 'jmc-sheet-user-uploaded-media', + "TOPICS_BUCKET": 'topicimages' } \ No newline at end of file From 6c1a224e98f6026b9bcff26bfe82fd7506627f75 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Wed, 13 Dec 2023 18:24:46 -0800 Subject: [PATCH 209/260] Updates Ways To Give pages for both languages to point to the most recent 990 form --- static/files/Sefaria_2022_990_Public.pdf | Bin 0 -> 539028 bytes static/js/StaticPages.jsx | 4 ++-- templates/static/he/ways-to-give.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 static/files/Sefaria_2022_990_Public.pdf diff --git a/static/files/Sefaria_2022_990_Public.pdf b/static/files/Sefaria_2022_990_Public.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9c62f136b151aed0c7f8cd8c1466501f99e28656 GIT binary patch literal 539028 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 Zr&K|X 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�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%0A=|h5=J7 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%0D=`;1Wnor~u6 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 N-doz(3~(Q4`Crk%Aaei! literal 0 HcmV?d00001 diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 09afb6f0c9..414dc2c947 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -1557,7 +1557,7 @@ const DonatePage = () => ( enTitle="Where does my gift go? How does Sefaria use the donations it receives?" heTitle="" enText="<p>Generally, gifts made to Sefaria are considered “unrestricted,” meaning that our staff allocates funds where they’re needed most. This includes everything from the text and learning you see on your screen to the technology support that keeps us online to the time and energy of the Sefaria team.</p> - <p><a href='https://www.guidestar.org/profile/46-4406454'>Sefaria has a Platinum rating on GuideStar</a> and we’re devoted to making sure we’re transparent and open with our donors. For a closer look at our financials, <a target='_blank' href='/static/files/Sefaria_2021_990_Public.pdf'>download the most recent Sefaria 990</a>.</p>" + <p><a href='https://www.guidestar.org/profile/46-4406454'>Sefaria has a Platinum rating on GuideStar</a> and we’re devoted to making sure we’re transparent and open with our donors. For a closer look at our financials, <a target='_blank' href='/static/files/Sefaria_2022_990_Public.pdf'>download the most recent Sefaria 990</a>.</p>" heText="" colorBar="#B8D4D3" /> @@ -1727,7 +1727,7 @@ const DonatePage = () => ( rounded={true} tall={false} newTab={true} - href="/static/files/Sefaria_2021_990_Public.pdf" + href="/static/files/Sefaria_2022_990_Public.pdf" he_href="" he="" en="See Here" diff --git a/templates/static/he/ways-to-give.html b/templates/static/he/ways-to-give.html index a9a0ab6c3a..1101c7639c 100644 --- a/templates/static/he/ways-to-give.html +++ b/templates/static/he/ways-to-give.html @@ -150,7 +150,7 @@ <h2> </header> <section> <span class="int-he"> - <a target="_blank" href="{% static "files/Sefaria_2020_990_Public.pdf" %}">דוח 990</a> + <a target="_blank" href="{% static "files/Sefaria_2022_990_Public.pdf" %}">דוח 990</a> </span> </section> <section> From c716d15ed0f4c6a30def8c07420b27e18b1686bc Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Wed, 13 Dec 2023 20:25:24 -0800 Subject: [PATCH 210/260] Change "non-profit" to "nonprofit" in all instances --- locale/en/LC_MESSAGES/django.po | 4 ++-- locale/he/LC_MESSAGES/django.po | 4 ++-- static/js/NavSidebar.jsx | 8 ++++---- static/js/StaticPages.jsx | 2 +- static/js/sefaria/strings.js | 2 +- templates/static/app.html | 2 +- templates/static/aramaic-translation-contest.html | 2 +- templates/static/dicta-thanks.html | 2 +- templates/static/en/about.html | 6 +++--- templates/static/he/about.html | 2 +- templates/static/home.html | 2 +- templates/static/jobs.html | 4 ++-- templates/static/mobile.html | 2 +- 13 files changed, 21 insertions(+), 21 deletions(-) diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index cc65939c5d..d215208623 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -1057,7 +1057,7 @@ msgstr "" #: templates/static/en/about.html:6 templates/static/he/about.html:6 msgid "" -"Sefaria is a non-profit organization dedicated to building the future of " +"Sefaria is a nonprofit organization dedicated to building the future of " "Jewish learning in an open and participatory way." msgstr "" @@ -1091,7 +1091,7 @@ msgstr "" #: templates/static/jobs.html:6 msgid "" -"Job openings at Sefaria, a non-profit technology organization dedicated to " +"Job openings at Sefaria, a nonprofit technology organization dedicated to " "building the Jewish future." msgstr "" diff --git a/locale/he/LC_MESSAGES/django.po b/locale/he/LC_MESSAGES/django.po index 1219a7332a..3228ed80a0 100644 --- a/locale/he/LC_MESSAGES/django.po +++ b/locale/he/LC_MESSAGES/django.po @@ -1132,7 +1132,7 @@ msgstr "אודות ספריא" #: templates/static/en/about.html:6 templates/static/he/about.html:6 msgid "" -"Sefaria is a non-profit organization dedicated to building the future of " +"Sefaria is a nonprofit organization dedicated to building the future of " "Jewish learning in an open and participatory way." msgstr "" "ספריא היא עמותה ללא מטרות רווה השואפת לעיצוב עתיד הלימוד היהודי כך שיהא פתוח " @@ -1168,7 +1168,7 @@ msgstr "משרות בספריא" #: templates/static/jobs.html:6 msgid "" -"Job openings at Sefaria, a non-profit technology organization dedicated to " +"Job openings at Sefaria, a nonprofit technology organization dedicated to " "building the Jewish future." msgstr "" "משרות פנויות בספריא, עמותה ללא מטרות רווח שמטרתה הבטחת העתיד הטכנולוגי של " diff --git a/static/js/NavSidebar.jsx b/static/js/NavSidebar.jsx index 69976798fe..6ea1d45507 100644 --- a/static/js/NavSidebar.jsx +++ b/static/js/NavSidebar.jsx @@ -97,7 +97,7 @@ const AboutSefaria = ({hideTitle}) => ( <ModuleTitle h1={true}>A Living Library of Torah</ModuleTitle> : null } <InterfaceText> <EnglishText> - Sefaria is home to 3,000 years of Jewish texts. We are a non-profit organization offering free access to texts, translations, + Sefaria is home to 3,000 years of Jewish texts. We are a nonprofit organization offering free access to texts, translations, and commentaries so that everyone can participate in the ongoing process of studying, interpreting, and creating Torah. </EnglishText> <HebrewText> @@ -135,7 +135,7 @@ const AboutTranslatedText = ({translationsSlug}) => { "fi": {title: "Tooran elävä kirjasto", body: "Sefaria on koti 3000 vuoden juutalaisille teksteille. Olemme voittoa tavoittelematon organisaatio, joka tarjoaa ilmaisen pääsyn teksteihin, käännöksiin ja kommentteihin, jotta kaikki voivat osallistua jatkuvaan Tooran opiskelu-, tulkkaus- ja luomisprosessiin."}, "fr": {title: "Une bibliothèque vivante de la Torah", body: "Une bibliothèque de Torah vivante. Sefaria abrite 3 000 ans de textes juifs. Nous sommes une organisation à but non lucratif offrant un accès gratuit aux textes de la Torah, aux commentaires et aux traductions, afin que chacun puisse participer au processus infini de l'étude, de l'interprétation et de la création de la Torah."}, "it": {title: "Una biblioteca vivente della Torah", body: "Sefaria ospita 3.000 anni di testi ebraici. Siamo un'organizzazione senza scopo di lucro che offre libero accesso a testi, traduzioni e commenti in modo che tutti possano partecipare al processo in corso di studio, interpretazione e creazione della Torah."}, - "pl": {title: "Żywa Biblioteka Tory", body: "Sefaria jest domem dla 3000 lat żydowskich tekstów. Jesteśmy organizacją non-profit oferującą bezpłatny dostęp do tekstów, tłumaczeń i komentarzy, dzięki czemu każdy może uczestniczyć w bieżącym procesie studiowania, tłumaczenia i tworzenia Tory."}, + "pl": {title: "Żywa Biblioteka Tory", body: "Sefaria jest domem dla 3000 lat żydowskich tekstów. Jesteśmy organizacją nonprofit oferującą bezpłatny dostęp do tekstów, tłumaczeń i komentarzy, dzięki czemu każdy może uczestniczyć w bieżącym procesie studiowania, tłumaczenia i tworzenia Tory."}, "pt": {title: "Uma Biblioteca Viva da Torá", body: "Sefaria é o lar de 3.000 anos de textos judaicos. Somos uma organização sem fins lucrativos que oferece acesso gratuito a textos, traduções e comentários para que todos possam participar do processo contínuo de estudo, interpretação e criação da Torá."}, "ru": {title: "Живая библиотека Торы", body: "Сефария является домом для еврейских текстов 3000-летней давности. Мы — некоммерческая организация, предлагающая бесплатный доступ к текстам, переводам и комментариям, чтобы каждый мог участвовать в продолжающемся процессе изучения, толкования и создания Торы."}, "yi": {title: "א לעבעדיקע ביבליאטעק פון תורה", body: "אין ספֿריאַ איז אַ היים פֿון 3,000 יאָר ייִדישע טעקסטן. מיר זענען אַ נאַן-נוץ אָרגאַניזאַציע וואָס אָפפערס פריי אַקסעס צו טעקסטן, איבערזעצונגען און קאָמענטאַרן אַזוי אַז אַלעמען קענען אָנטייל נעמען אין די אָנגאָינג פּראָצעס פון לערנען, ינטערפּריטיישאַן און שאפן תורה."} @@ -148,7 +148,7 @@ const AboutTranslatedText = ({translationsSlug}) => { translationLookup[translationsSlug]["body"] : <InterfaceText> <EnglishText> - Sefaria is home to 3,000 years of Jewish texts. We are a non-profit organization offering free access to texts, translations, + Sefaria is home to 3,000 years of Jewish texts. We are a nonprofit organization offering free access to texts, translations, and commentaries so that everyone can participate in the ongoing process of studying, interpreting, and creating Torah. </EnglishText> <HebrewText> @@ -191,7 +191,7 @@ const TheJewishLibrary = ({hideTitle}) => ( const SupportSefaria = ({blue}) => ( <Module blue={blue}> <ModuleTitle>Support Sefaria</ModuleTitle> - <InterfaceText>Sefaria is an open source, non-profit project. Support us by making a tax-deductible donation.</InterfaceText> + <InterfaceText>Sefaria is an open source, nonprofit project. Support us by making a tax-deductible donation.</InterfaceText> <br /> <DonateLink classes={"button small" + (blue ? " white" : "")} source={"NavSidebar-SupportSefaria"}> <img src="/static/img/heart.png" alt="donation icon" /> diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 09afb6f0c9..1c38f1ae1b 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -1973,7 +1973,7 @@ const PoweredByPage = () => ( /> <Feature enTitle="Dicta" - enText="Dicta is a non-profit research organization based in Israel that applies cutting-edge machine learning and natural language processing (the ability of a computer program to understand human language as it is spoken and written) to the analysis of Hebrew texts. Sefaria and Dicta often collaborate, sharing texts and splitting the costs of shared projects. Dicta offers a broad range of tools for free use by anyone, including the ability to add nikud (vocalization) to text as you type, intuitive Talmud and Bible search, and more." + enText="Dicta is a nonprofit research organization based in Israel that applies cutting-edge machine learning and natural language processing (the ability of a computer program to understand human language as it is spoken and written) to the analysis of Hebrew texts. Sefaria and Dicta often collaborate, sharing texts and splitting the costs of shared projects. Dicta offers a broad range of tools for free use by anyone, including the ability to add nikud (vocalization) to text as you type, intuitive Talmud and Bible search, and more." enImg="/static/img/powered-by-landing-page/talmudsearch.dicta.org.il_.png" enImgAlt="Screenshot of Dicta" borderColor={palette.colors.lightblue} diff --git a/static/js/sefaria/strings.js b/static/js/sefaria/strings.js index c1998554e2..462214f6b9 100644 --- a/static/js/sefaria/strings.js +++ b/static/js/sefaria/strings.js @@ -35,7 +35,7 @@ const Strings = { "Stay Connected": "הישארו מעודכנים", "Get updates on new texts, learning resources, features, and more.": "קבלו עדכונים על מקורות חדשים, כלי למידה חדשים ועוד.", "Support Sefaria": "תמכו בספריא", - "Sefaria is an open source, non-profit project. Support us by making a tax-deductible donation.": "ספריא היא מאגר פתוח וחינמי. תמכו בנו בעזרת תרומה.", + "Sefaria is an open source, nonprofit project. Support us by making a tax-deductible donation.": "ספריא היא מאגר פתוח וחינמי. תמכו בנו בעזרת תרומה.", "Make a Donation": "לתרומה", "Join the Conversation": "קחו חלק בשיח" , "Explore the Community": "לעמוד הקהילה", diff --git a/templates/static/app.html b/templates/static/app.html index 4c55712ceb..ef2f6884d7 100644 --- a/templates/static/app.html +++ b/templates/static/app.html @@ -334,7 +334,7 @@ <h3 class="mobileNewsletterHeader"> </div> <div id="mobilePageNonProfit"> <p class="int-en"> - Sefaria is a 501(c)(3) non-profit organization that creates open source software and publishes digital texts with open licenses. + Sefaria is a 501(c)(3) nonprofit organization that creates open source software and publishes digital texts with open licenses. </p> <p class="int-he"> ספריא הוא אירגון ללא מטרת רווח לפי חוקי ארצות הברית אשר יוצר תוכנות חיפוש למקורות פתוחים לשימוש ומפרסם טקסטים דיגיטליים שאין עליהם זכויות יוצרים. diff --git a/templates/static/aramaic-translation-contest.html b/templates/static/aramaic-translation-contest.html index 515bef5f99..1511878c80 100644 --- a/templates/static/aramaic-translation-contest.html +++ b/templates/static/aramaic-translation-contest.html @@ -154,7 +154,7 @@ <h2> <span class="int-he">אנו זקוקים לעזרתכם</span> </h2> <div class="description"> - <span class="int-en">Sefaria is an open source, non-profit project. Support us by making a tax-deductible donation.</span> + <span class="int-en">Sefaria is an open source, nonprofit project. Support us by making a tax-deductible donation.</span> <span class="int-he">פרויקט ספריא פתוח לקהל הרחב (open source) ללא מטרות רווח. תמכו בנו באמצעות תרומה פטורה ממס.</span> </div> <a href="/donate"> diff --git a/templates/static/dicta-thanks.html b/templates/static/dicta-thanks.html index a7cd48cbd9..df5ca09815 100644 --- a/templates/static/dicta-thanks.html +++ b/templates/static/dicta-thanks.html @@ -30,7 +30,7 @@ <h1> </span> </p> <p> - <span class="int-en">Dicta is a non-profit organization that provides its products at no charge for the benefit of the public.</span> + <span class="int-en">Dicta is a nonprofit organization that provides its products at no charge for the benefit of the public.</span> <span class="int-he">דיקטה היא עמותה ללא מטרת רווח המספקת את מוצריה ללא תשלום לטובת הכלל.</span> </p> <p> diff --git a/templates/static/en/about.html b/templates/static/en/about.html index 105bb9b8b5..4672781660 100644 --- a/templates/static/en/about.html +++ b/templates/static/en/about.html @@ -3,7 +3,7 @@ {% block title %}{% trans "About Sefaria" %}{% endblock %} -{% block description %}{% trans "Sefaria is a non-profit organization dedicated to building the future of Jewish learning in an open and participatory way." %}{% endblock %} +{% block description %}{% trans "Sefaria is a nonprofit organization dedicated to building the future of Jewish learning in an open and participatory way." %}{% endblock %} {% block css %} #top #languageToggle { @@ -47,7 +47,7 @@ <h1 id="aboutTitle"> </span> </p> <p> - <span class="int-en">Sefaria is a non-profit organization dedicated to building the future of Jewish learning in an open and participatory way. + <span class="int-en">Sefaria is a nonprofit organization dedicated to building the future of Jewish learning in an open and participatory way. We are assembling a free living library of Jewish texts and their interconnections, in Hebrew and in translation. With these digital texts, we can create new, interactive interfaces for Web, tablet and mobile, @@ -131,7 +131,7 @@ <h1> </span> </section> <section class="historyItem"> - <span class="int-en">Sefaria incorporates as a non-profit and hires its first employee – who remains at the company to this day!</span> + <span class="int-en">Sefaria incorporates as a nonprofit and hires its first employee – who remains at the company to this day!</span> </section> <section class="historyItem"> <span class="int-en">The Sefaria library grows to 8.5 million words.</span> diff --git a/templates/static/he/about.html b/templates/static/he/about.html index a936311229..16d3ce6e49 100644 --- a/templates/static/he/about.html +++ b/templates/static/he/about.html @@ -3,7 +3,7 @@ {% block title %}{% trans "About Sefaria" %}{% endblock %} -{% block description %}{% trans "Sefaria is a non-profit organization dedicated to building the future of Jewish learning in an open and participatory way." %}{% endblock %} +{% block description %}{% trans "Sefaria is a nonprofit organization dedicated to building the future of Jewish learning in an open and participatory way." %}{% endblock %} {% block css %} #top #languageToggle { diff --git a/templates/static/home.html b/templates/static/home.html index 0116a5a18f..e795bb1840 100644 --- a/templates/static/home.html +++ b/templates/static/home.html @@ -336,7 +336,7 @@ <h2> <span class="int-he">אנו זקוקים לעזרתכם</span> </h2> <div class="description systemText"> - <span class="int-en">Sefaria is an open source, non-profit project. Support us by making a tax-deductible donation.</span> + <span class="int-en">Sefaria is an open source, nonprofit project. Support us by making a tax-deductible donation.</span> <span class="int-he">פרויקט ספריא פתוח לקהל הרחב (open source) ללא מטרות רווח. תמכו בנו באמצעות תרומה פטורה ממס.</span> </div> <a href="/donate"> diff --git a/templates/static/jobs.html b/templates/static/jobs.html index 0b341814f7..b47f4d244b 100644 --- a/templates/static/jobs.html +++ b/templates/static/jobs.html @@ -3,7 +3,7 @@ {% block title %}{% trans "Jobs at Sefaria" %}{% endblock %} -{% block description %}{% trans "Job openings at Sefaria, a non-profit technology organization dedicated to building the Jewish future." %}{% endblock %} +{% block description %}{% trans "Job openings at Sefaria, a nonprofit technology organization dedicated to building the Jewish future." %}{% endblock %} {% block content %} @@ -22,7 +22,7 @@ <h1 class="serif"> </h2> <p> <span class="int-en"> - Sefaria is a non-profit organization dedicated to creating the future of Torah in an open and + Sefaria is a nonprofit organization dedicated to creating the future of Torah in an open and participatory way. We are assembling a free, living library of Jewish texts and their interconnections, in Hebrew and in translation. diff --git a/templates/static/mobile.html b/templates/static/mobile.html index e7943735fe..1137d6c52c 100644 --- a/templates/static/mobile.html +++ b/templates/static/mobile.html @@ -339,7 +339,7 @@ <h3 class="mobileNewsletterHeader"> </div> <div id="mobilePageNonProfit"> <p class="int-en"> - Sefaria is a 501(c)(3) non-profit organization that creates open source software and publishes digital texts with open licenses. + Sefaria is a 501(c)(3) nonprofit organization that creates open source software and publishes digital texts with open licenses. </p> <p class="int-he"> ספריא הוא אירגון ללא מטרת רווח לפי חוקי ארצות הברית אשר יוצר תוכנות חיפוש למקורות פתוחים לשימוש ומפרסם טקסטים דיגיטליים שאין עליהם זכויות יוצרים. From 1e9cb5bd46fd9762c72dc05eafa4c36ca9b226ed Mon Sep 17 00:00:00 2001 From: Skyler C <skyler@sefaria.org> Date: Thu, 14 Dec 2023 13:47:18 -0500 Subject: [PATCH 211/260] Fix merge issue and change last remaining "non-profit" string --- static/js/StaticPages.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/StaticPages.jsx b/static/js/StaticPages.jsx index 3d0bf92187..005337d866 100644 --- a/static/js/StaticPages.jsx +++ b/static/js/StaticPages.jsx @@ -2904,7 +2904,7 @@ const JobsPageHeader = ({ jobsAreAvailable }) => { </h2> <p> <span className="int-en"> - Sefaria is a non-profit organization dedicated to creating the + Sefaria is a nonprofit organization dedicated to creating the future of Torah in an open and participatory way. We are assembling a free, living library of Jewish texts and their interconnections, in Hebrew and in translation. From 88f0c7daaa8060a01deb2d05e69e457b5f89e2b7 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Sun, 17 Dec 2023 15:15:39 +0200 Subject: [PATCH 212/260] chore(helm): Add Varnish cfg for v3 text api endpoint --- .../sefaria-project/templates/configmap/varnish-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/helm-chart/sefaria-project/templates/configmap/varnish-config.yaml b/helm-chart/sefaria-project/templates/configmap/varnish-config.yaml index d2c5670f13..c293c3963e 100644 --- a/helm-chart/sefaria-project/templates/configmap/varnish-config.yaml +++ b/helm-chart/sefaria-project/templates/configmap/varnish-config.yaml @@ -44,6 +44,7 @@ data: if ((req.method == "GET") && ( (req.url ~"^/api/texts" && req.url !~"^/api/texts/versions/" && req.url !~"^/api/texts/parashat_hashavua" && req.url !~"^/api/texts/random" && req.url !~"layer=") + || req.url ~"^/api/v3/texts" || req.url ~"^/api/texts/version-status/tree/" || req.url ~"^/api/links/bare" || req.url ~"^/api/links" From 5cb18881922142b53cf17660d1cc97ec9ded5987 Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Mon, 18 Dec 2023 15:47:11 +0200 Subject: [PATCH 213/260] chore(otel): Disable otel (temporarily?) --- build/web/Dockerfile | 2 +- requirements.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/web/Dockerfile b/build/web/Dockerfile index 7065e16cee..df5a02be51 100644 --- a/build/web/Dockerfile +++ b/build/web/Dockerfile @@ -4,7 +4,7 @@ ARG TYPE=build-prod WORKDIR /app/ # Copied separately to allow for caching of the `pip install` build step COPY requirements.txt /app/requirements.txt -RUN pip3 install --no-cache-dir -r /app/requirements.txt && opentelemetry-bootstrap -a install +RUN pip3 install --no-cache-dir -r /app/requirements.txt COPY package*.json /app/ RUN npm install --unsafe-perm diff --git a/requirements.txt b/requirements.txt index df83dfb6ee..bb35b78e5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -67,7 +67,7 @@ python-bidi requests Cerberus -opentelemetry-distro -opentelemetry-exporter-otlp -opentelemetry-propagator-b3 -opentelemetry-propagator-jaeger +#opentelemetry-distro +#opentelemetry-exporter-otlp +#opentelemetry-propagator-b3 +#opentelemetry-propagator-jaeger From b85856e5a47513a6409ffd1348f9d93d90a3d64b Mon Sep 17 00:00:00 2001 From: Ephraim <ephraim@sefaria.org> Date: Tue, 19 Dec 2023 10:10:06 +0200 Subject: [PATCH 214/260] test: Fix broken test on Baal Haturim the index record was renamed to Ba'al Haturim --- sefaria/tests/texts_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/tests/texts_test.py b/sefaria/tests/texts_test.py index 4273bc465e..ce1b2eee90 100644 --- a/sefaria/tests/texts_test.py +++ b/sefaria/tests/texts_test.py @@ -26,7 +26,7 @@ def test_rename_category(): def test_get_commentary_texts_list(): l = tm.library.get_dependant_indices() - assert 'Baal HaTurim on Genesis' in l + assert "Ba'al HaTurim on Genesis" in l assert 'Bartenura on Mishnah Eduyot' in l assert 'Tosafot on Pesachim' in l From d7c83b8440ff1613b93972986a7c1d5e3205d178 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Tue, 19 Dec 2023 13:48:39 +0200 Subject: [PATCH 215/260] fix(translations): add 1 to maxHeight for cases where for some reason scrollHeight is bigger by 1. --- static/js/VersionBlockHeader.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlockHeader.jsx index 2b4207d3ce..31ce43baa7 100644 --- a/static/js/VersionBlockHeader.jsx +++ b/static/js/VersionBlockHeader.jsx @@ -47,7 +47,7 @@ function VersionBlockHeaderText({link, onClick, text, direction}) { const element = textRef.current; const computedStyles = window.getComputedStyle(element); const maxHeight = parseInt(computedStyles.getPropertyValue('max-height'), 10); - setTruncationOccurred(element.scrollHeight > maxHeight); + setTruncationOccurred(element.scrollHeight > maxHeight+1); //added +1 because for some reason when the view is too big the height has 1 more }); //no second param for running in resize seems better than adding a listener function onEllipsisClick() { setShouldAttemptTruncation(false); From 37eef42373dda78b5533519f2cdb12ede428f468 Mon Sep 17 00:00:00 2001 From: yonadavGit <yonadav.le@gmail.com> Date: Tue, 19 Dec 2023 16:49:38 +0200 Subject: [PATCH 216/260] fix(copyFromReader): first approach for fixing - remove spans from text segment. recognize spans to remove through their ancestors --- static/js/ReaderApp.jsx | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/static/js/ReaderApp.jsx b/static/js/ReaderApp.jsx index e372ced53e..20e1c0731f 100644 --- a/static/js/ReaderApp.jsx +++ b/static/js/ReaderApp.jsx @@ -1897,6 +1897,86 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { poetryElsToCollapse.forEach(poetryEl => { poetryEl.outerHTML = poetryEl.innerHTML; }); + // function removeClassesFromSpans(element) { + // // Remove classes from the current element + // if (element.tagName === 'SPAN') { + // element.className = ''; + // } + // // Recursively process child elements + // for (let i = 0; i < element.children.length; i++) { + // removeClassesFromSpans(element.children[i]); + // } + // } + // function removeSpansKeepText(element, classListToRemove) { + // // Iterate over the child nodes in reverse order to handle removal + // for (let i = element.children.length - 1; i >= 0; i--) { + // const child = element.children[i]; + // + // // Check if the child is a span + // if (child.tagName === 'SPAN' && classListToRemove.includes(child.className)) { + // // Replace the span with its text content + // const textNode = document.createTextNode(child.textContent); + // element.replaceChild(textNode, child); + // } else { + // // Recursively process non-span child elements + // removeSpansKeepText(child, classListToRemove); + // } + // } + // } + // removeSpansKeepText(container, ["mam-kq-trivial"]); + function removeSpansWithAncestorP(element) { + // Base case: If the current element is a text node, return its text content + if (element.nodeType === Node.TEXT_NODE) { + return element.textContent; + } + + // Check if the current element is a <span> and has a <p> ancestor + if (element.nodeName === 'SPAN' && hasAncestorP(element)) { + // Replace the <span> with its text content + return element.textContent; + } + + // Recursively process child nodes + for (let i = 0; i < element.childNodes.length; i++) { + const childResult = removeSpansWithAncestorP(element.childNodes[i]); + + // Replace the child node with its processed content + if (childResult) { + if (element.childNodes[i].nodeType === Node.TEXT_NODE) { + element.replaceChild(document.createTextNode(childResult), element.childNodes[i]); + } else { + element.replaceChild(createFragmentFromHTML(childResult), element.childNodes[i]); + } + } + } + + // Return the processed content of the current element + return element.outerHTML; + } + + function hasAncestorP(element) { + let ancestor = element.parentNode; + while (ancestor) { + if (ancestor.nodeName === 'P') { + return true; + } + ancestor = ancestor.parentNode; + } + return false; + } + + function createFragmentFromHTML(htmlString) { + const fragment = document.createDocumentFragment(); + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = htmlString; + + while (tempDiv.firstChild) { + fragment.appendChild(tempDiv.firstChild); + } + + return fragment; + } + removeSpansWithAncestorP(container) // Remove extra breaks for continuous mode if (closestReaderPanel && closestReaderPanel.classList.contains('continuous')) { @@ -1940,6 +2020,9 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { html = container.outerHTML; textOnly = Sefaria.util.htmlToText(html); selectedEls = container; + console.log(container) + console.log(html) + console.log(textOnly) } From b34f86dcd5cf040379013aed063cbb125ce48331 Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Wed, 20 Dec 2023 09:06:28 +0200 Subject: [PATCH 217/260] helm: remove superfluous double spaces! --- .../templates/configmap/varnish-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/helm-chart/sefaria-project/templates/configmap/varnish-config.yaml b/helm-chart/sefaria-project/templates/configmap/varnish-config.yaml index c293c3963e..1959feefb2 100644 --- a/helm-chart/sefaria-project/templates/configmap/varnish-config.yaml +++ b/helm-chart/sefaria-project/templates/configmap/varnish-config.yaml @@ -30,16 +30,16 @@ data: return (pass); } if (req.http.User-Agent ~ "MegaIndex") { - return(synth(403, "Your spider is not honoring our robots.txt crawl-delay. Banning.")); + return(synth(403, "Your spider is not honoring our robots.txt crawl-delay. Banning.")); } if (req.http.User-Agent ~ "^Sogou") { - return(synth(403, "Your spider has put too much load on our server. Banning.")); + return(synth(403, "Your spider has put too much load on our server. Banning.")); } if (req.http.User-Agent ~ "SemrushBot") { - return(synth(403, "Your spider has put too much load on our server. Banning.")); + return(synth(403, "Your spider has put too much load on our server. Banning.")); } if (req.http.User-Agent ~ "YandexBot") { - return(synth(403, "Your spider has put too much load on our server. Banning.")); + return(synth(403, "Your spider has put too much load on our server. Banning.")); } if ((req.method == "GET") && ( From 9bf885e268936800d6db38955ee4355dbebbf12c Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 20 Dec 2023 11:18:32 +0200 Subject: [PATCH 218/260] refactor(translation): move functions to the root of sefaria.js --- static/js/sefaria/sefaria.js | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/static/js/sefaria/sefaria.js b/static/js/sefaria/sefaria.js index 558b4dd938..16d4e456dd 100644 --- a/static/js/sefaria/sefaria.js +++ b/static/js/sefaria/sefaria.js @@ -487,29 +487,29 @@ Sefaria = extend(Sefaria, { return Promise.all(promises).then(results => Object.assign({}, ...results)); }, - getTextsFromAPIV3: async function(ref, requiredVersions, mergeText) { - // ref is segment ref or bottom level section ref - // requiredVersions is array of objects that can have language and versionTitle - function makeParamsString(language, versionTitle) { - if (versionTitle) { + makeParamsStringForAPIV3: function(language, versionTitle) { + if (versionTitle) { return `${language}|${versionTitle}`; - } else if (language) { + } else if (language) { return language; - } - } - function makeUrl() { - const host = Sefaria.apiHost; - const endPoint = '/api/v3/texts/' - const versions = requiredVersions.map(obj => - makeParamsString(obj.language, obj.versionTitle) - ); - const url = `${host}${endPoint}${ref}?version=${versions.join('&version=')}&fill_in_missing_segments=${mergeText}`; - return url; } - const url = makeUrl(ref, requiredVersions); - //here's the place for getting it from cache + }, + makeUrlForAPIV3Text: function(ref, requiredVersions, mergeText) { + const host = Sefaria.apiHost; + const endPoint = '/api/v3/texts/'; + const versions = requiredVersions.map(obj => + Sefaria.makeParamsStringForAPIV3(obj.language, obj.versionTitle) + ); + const url = `${host}${endPoint}${ref}?version=${versions.join('&version=')}&fill_in_missing_segments=${mergeText}`; + return url; + }, + getTextsFromAPIV3: async function(ref, requiredVersions, mergeText) { + // ref is segment ref or bottom level section ref + // requiredVersions is array of objects that can have language and versionTitle + const url = Sefaria.makeUrlForAPIV3Text(ref, requiredVersions, mergeText); + //TODO here's the place for getting it from cache const apiObject = await Sefaria._ApiPromise(url); - //here's the place for all changes we want to add, and saving in cache + //TODO here's the place for all changes we want to add, and saving in cache return apiObject; }, getAllTranslationsWithText: async function(ref) { From 873aeef40d2f916f17bf710f9c8a88197cb89683 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 20 Dec 2023 11:36:32 +0200 Subject: [PATCH 219/260] refactor(translation): rename versionTools to VersionBlockUtils --- static/js/VersionBlock.jsx | 16 ++++++++-------- static/js/VersionBlockWithPreview.jsx | 6 +++--- static/js/VersionBlockWithPreviewTitleLine.jsx | 6 +++--- static/js/VersionDetailsImage.jsx | 4 ++-- static/js/VersionDetailsInformation.jsx | 12 ++++++------ static/js/VersionPreviewMeta.jsx | 4 ++-- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index 1e4164925d..969c6cd2c3 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -12,7 +12,7 @@ import VersionDetailsInformation from "./VersionDetailsInformation"; import VersionDetailsImage from "./VersionDetailsImage"; import VersionBlockWithPreview from "./VersionBlockWithPreview"; -class versionTools { +class VersionBlockUtils { static makeVersionTitle(version){ if (version.merged) { return {"className" : "", "text": Sefaria._("Merged from") + " " + Array.from(new Set(version.sources)).join(", ")}; @@ -198,12 +198,12 @@ class VersionBlock extends Component { render() { if(this.props.version.title == "Sheet") return null //why are we even getting here in such a case??; const v = this.props.version; - const vtitle = versionTools.makeVersionTitle(v); + const vtitle = VersionBlockUtils.makeVersionTitle(v); const vnotes = this.makeVersionNotes(); const showLanguagLabel = this.props.rendermode == "book-page"; - const openVersionInSidebar = versionTools.openVersionInSidebar.bind(null, this.props.currentRef, this.props.version, + const openVersionInSidebar = VersionBlockUtils.openVersionInSidebar.bind(null, this.props.currentRef, this.props.version, this.props.currObjectVersions, this.props.openVersionInSidebar); - const openVersionInMainPanel = versionTools.openVersionInMainPanel.bind(null, this.props.currentRef, + const openVersionInMainPanel = VersionBlockUtils.openVersionInMainPanel.bind(null, this.props.currentRef, this.props.version, this.props.currObjectVersions, this.props.rendermode, this.props.firstSectionRef, this.props.openVersionInReader); if (this.state.editing && Sefaria.is_moderator) { @@ -278,7 +278,7 @@ class VersionBlock extends Component { text={vtitle["text"]} onClick={this.props.rendermode === 'book-page' ? openVersionInMainPanel : openVersionInSidebar} renderMode='versionTitle' - link={versionTools.makeVersionLink(this.props.currentRef, this.props.version, + link={VersionBlockUtils.makeVersionLink(this.props.currentRef, this.props.version, this.props.currObjectVersions, this.props.rendermode === 'book-page')} /> </div> @@ -290,11 +290,11 @@ class VersionBlock extends Component { isSelected={this.props.isCurrent} openVersionInMainPanel={openVersionInMainPanel} text={this.makeSelectVersionLanguage()} - link={versionTools.makeVersionLink(this.props.currentRef, this.props.version, + link={VersionBlockUtils.makeVersionLink(this.props.currentRef, this.props.version, this.props.currObjectVersions, true)} /> </div> - <div className={classNames(versionTools.makeAttrClassNames(v, {"versionNotes": 1, "sans-serif": (this.props.rendermode == "book-page")}, "versionNotes", true))}> + <div className={classNames(VersionBlockUtils.makeAttrClassNames(v, {"versionNotes": 1, "sans-serif": (this.props.rendermode == "book-page")}, "versionNotes", true))}> <span className="" dangerouslySetInnerHTML={ {__html: vnotes} } /> <span className={`versionExtendedNotesLinks ${this.hasExtendedNotes() ? "": "n-a"}`}> <a onClick={this.openExtendedNotes} href={`/${this.props.version.title}/${this.props.version.language}/${this.props.version.versionTitle}/notes`}> @@ -459,4 +459,4 @@ VersionsBlocksList.defaultProps = { -export {VersionBlock as default, VersionsBlocksList, versionTools}; +export {VersionBlock as default, VersionsBlocksList, VersionBlockUtils}; diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlockWithPreview.jsx index bf2d8b2395..7af8326bb7 100644 --- a/static/js/VersionBlockWithPreview.jsx +++ b/static/js/VersionBlockWithPreview.jsx @@ -1,13 +1,13 @@ import React, {useEffect, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import VersionBlockHeader from "./VersionBlockHeader"; -import {versionTools} from './VersionBlock'; +import {VersionBlockUtils} from './VersionBlock'; import VersionBlockWithPreviewTitleLine from './VersionBlockWithPreviewTitleLine'; import VersionPreviewMeta from "./VersionPreviewMeta"; import {OpenConnectionTabButton} from "./TextList"; function VersionBlockWithPreview({currentRef, version, currObjectVersions, openVersionInSidebar, openVersionInReader, isSelected, srefs, onRangeClick}) { - const opeInSidebar = versionTools.openVersionInSidebar.bind(null, currentRef, version, currObjectVersions, openVersionInSidebar); + const opeInSidebar = VersionBlockUtils.openVersionInSidebar.bind(null, currentRef, version, currObjectVersions, openVersionInSidebar); function openInTabCallback(sref) { onRangeClick(sref, false, {[version.language]: version.versionTitle}); } @@ -17,7 +17,7 @@ function VersionBlockWithPreview({currentRef, version, currObjectVersions, openV text={version.text} onClick={opeInSidebar} renderMode='contentText' - link={versionTools.makeVersionLink(currentRef, version, currObjectVersions, false)} + link={VersionBlockUtils.makeVersionLink(currentRef, version, currObjectVersions, false)} direction={version.direction || 'ltr'} /> <details> diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionBlockWithPreviewTitleLine.jsx index 8c217310f7..9d39ee7935 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionBlockWithPreviewTitleLine.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import VersionBlockSelectButton from "./VersionBlockSelectButton"; -import {versionTools} from './VersionBlock'; +import {VersionBlockUtils} from './VersionBlock'; import Sefaria from "./sefaria/sefaria"; function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersions, openVersionInReader, isSelected}) { @@ -12,7 +12,7 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio } return shortVersionTitle; } - const openVersionInMainPanel = versionTools.openVersionInMainPanel.bind(null, currentRef, version, currObjectVersions, 'select-button', + const openVersionInMainPanel = VersionBlockUtils.openVersionInMainPanel.bind(null, currentRef, version, currObjectVersions, 'select-button', null, openVersionInReader); const buttonText = isSelected ? 'Currently Selected' : 'Select'; return ( @@ -24,7 +24,7 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio isSelected={isSelected} openVersionInMainPanel={openVersionInMainPanel} text={buttonText} - link={versionTools.makeVersionLink(currentRef, version, currObjectVersions, true)} + link={VersionBlockUtils.makeVersionLink(currentRef, version, currObjectVersions, true)} /> </div> ); diff --git a/static/js/VersionDetailsImage.jsx b/static/js/VersionDetailsImage.jsx index d68b98e801..0e06692609 100644 --- a/static/js/VersionDetailsImage.jsx +++ b/static/js/VersionDetailsImage.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from "classnames"; import Sefaria from "./sefaria/sefaria"; -import {versionTools} from "./VersionBlock"; +import {VersionBlockUtils} from "./VersionBlock"; function VersionDetailsImage({version}) { function makeImageLink() { @@ -13,7 +13,7 @@ function VersionDetailsImage({version}) { } return ( <div className="versionDetailsImage"> - <div className={classNames(versionTools.makeAttrClassNames(version, {"versionBuyImage": 1, "versionDetailsElement": 1} , "purchaseInformationImage"))}> + <div className={classNames(VersionBlockUtils.makeAttrClassNames(version, {"versionBuyImage": 1, "versionDetailsElement": 1} , "purchaseInformationImage"))}> <a className="versionDetailsLink versionDetailsImageLink" href={makeImageLink()} target="_blank"> <img className="versionImage" src={makeImageSrc()} alt={Sefaria._("Buy Now")} /> </a> diff --git a/static/js/VersionDetailsInformation.jsx b/static/js/VersionDetailsInformation.jsx index b2cba54de9..7814979727 100644 --- a/static/js/VersionDetailsInformation.jsx +++ b/static/js/VersionDetailsInformation.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from "classnames"; import Sefaria from "./sefaria/sefaria"; -import {versionTools} from './VersionBlock'; +import {VersionBlockUtils} from './VersionBlock'; function VersionDetailsInformation({currentRef, version}) { function makeLicenseLink() { @@ -11,7 +11,7 @@ function VersionDetailsInformation({currentRef, version}) { } return ( <div className="versionDetailsInformation"> - <div className={classNames(versionTools.makeAttrClassNames(version, {"versionSource": 1, "versionDetailsElement": 1}, "versionSource"))}> + <div className={classNames(VersionBlockUtils.makeAttrClassNames(version, {"versionSource": 1, "versionDetailsElement": 1}, "versionSource"))}> <span className="versionDetailsLabel"> {`${Sefaria._("Source")}: `} </span> @@ -19,7 +19,7 @@ function VersionDetailsInformation({currentRef, version}) { { Sefaria.util.parseUrl(version.versionSource).host.replace("www.", "") } </a> </div> - <div className={classNames(versionTools.makeAttrClassNames(version, {"versionDigitizedBySefaria": 1, "versionDetailsElement": 1}, "digitizedBySefaria"))}> + <div className={classNames(VersionBlockUtils.makeAttrClassNames(version, {"versionDigitizedBySefaria": 1, "versionDetailsElement": 1}, "digitizedBySefaria"))}> <span className="versionDetailsLabel"> {`${Sefaria._("Digitization")}: `} < /span> @@ -27,7 +27,7 @@ function VersionDetailsInformation({currentRef, version}) { {Sefaria._("Sefaria")} </a> </div> - <div className={classNames(versionTools.makeAttrClassNames(version, {"versionLicense": 1, "versionDetailsElement": 1}, "license" ))}> + <div className={classNames(VersionBlockUtils.makeAttrClassNames(version, {"versionLicense": 1, "versionDetailsElement": 1}, "license" ))}> <span className="versionDetailsLabel"> {`${Sefaria._("License")}: `} </span> @@ -35,12 +35,12 @@ function VersionDetailsInformation({currentRef, version}) { {Sefaria._(version?.license)} </a> </div> - <div className={classNames(versionTools.makeAttrClassNames(version, {"versionHistoryLink": 1, "versionDetailsElement": 1}, null))}> + <div className={classNames(VersionBlockUtils.makeAttrClassNames(version, {"versionHistoryLink": 1, "versionDetailsElement": 1}, null))}> <a className="versionDetailsLink" href={`/activity/${Sefaria.normRef(currentRef)}/${version.language}/${version.versionTitle && version.versionTitle.replace(/\s/g,"_")}`} target="_blank"> {Sefaria._("Revision History")} </a> </div> - <div className={classNames(versionTools.makeAttrClassNames(version, {"versionBuyLink": 1, "versionDetailsElement": 1}, "purchaseInformationURL"))}> + <div className={classNames(VersionBlockUtils.makeAttrClassNames(version, {"versionBuyLink": 1, "versionDetailsElement": 1}, "purchaseInformationURL"))}> <a className="versionDetailsLink" href={version.purchaseInformationURL} target="_blank"> {Sefaria._("Buy in Print")} </a> diff --git a/static/js/VersionPreviewMeta.jsx b/static/js/VersionPreviewMeta.jsx index 2d52d197d8..76e0c1d2e1 100644 --- a/static/js/VersionPreviewMeta.jsx +++ b/static/js/VersionPreviewMeta.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {versionTools} from "./VersionBlock"; +import {VersionBlockUtils} from "./VersionBlock"; import VersionDetailsInformation from "./VersionDetailsInformation"; import VersionDetailsImage from "./VersionDetailsImage"; @@ -8,7 +8,7 @@ function VersionPreviewMeta({currentRef, version}) { return ( <div className='versionDetails preview'> <div className='version-preview-informations'> - <div className='versionDetails-version-title'>{versionTools.makeVersionTitle(version).text}</div> + <div className='versionDetails-version-title'>{VersionBlockUtils.makeVersionTitle(version).text}</div> <VersionDetailsInformation currentRef={currentRef} version={version}/> </div> <VersionDetailsImage version={version}/> From 9f9da158348200ea99cd50ba6396c57bc2f900d3 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 20 Dec 2023 11:42:45 +0200 Subject: [PATCH 220/260] refactor(translation): use openStrings in OpenConnectionTabButton rather than renderMode --- static/js/TextList.jsx | 4 ++-- static/js/VersionBlockWithPreview.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/TextList.jsx b/static/js/TextList.jsx index 92c42638dc..9055fafc9d 100644 --- a/static/js/TextList.jsx +++ b/static/js/TextList.jsx @@ -312,13 +312,13 @@ const DeleteConnectionButton = ({delUrl, connectionDeleteCallback}) =>{ } -const OpenConnectionTabButton = ({srefs, openInTabCallback, renderMode}) =>{ +const OpenConnectionTabButton = ({srefs, openInTabCallback, openStrings}) =>{ /* ConnectionButton composite element. Goes inside a ConnectionButtons Takes a ref(s) for opening as a link and callback for opening in-app */ const sref = Array.isArray(srefs) ? Sefaria.normRefList(srefs) : srefs; - const [en, he] = renderMode === 'versionPreview' ? ['Open Text', 'פתיחת טקסט'] : ['Open', 'פתיחה']; + const [en, he] = openStrings || ['Open', 'פתיחה']; const openLinkInTab = (event) => { if (openInTabCallback) { event.preventDefault(); diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlockWithPreview.jsx index 7af8326bb7..49f744bfa1 100644 --- a/static/js/VersionBlockWithPreview.jsx +++ b/static/js/VersionBlockWithPreview.jsx @@ -38,7 +38,7 @@ function VersionBlockWithPreview({currentRef, version, currObjectVersions, openV <OpenConnectionTabButton srefs={srefs} openInTabCallback={openInTabCallback} - renderMode='versionPreview' + openStrings={['Open Text', 'פתיחת טקסט']} /> </div> </details> From 59733e080bf2bff69a75b01a33205bf7e812e97c Mon Sep 17 00:00:00 2001 From: yonadavGit <yonadav.le@gmail.com> Date: Wed, 20 Dec 2023 13:02:39 +0200 Subject: [PATCH 221/260] fix(copyFromReader): second and cleaner approach - remove all spans that are not "rangeSpan" from copied selection --- static/js/ReaderApp.jsx | 83 ++--------------------------------------- 1 file changed, 3 insertions(+), 80 deletions(-) diff --git a/static/js/ReaderApp.jsx b/static/js/ReaderApp.jsx index 20e1c0731f..8f55710f34 100644 --- a/static/js/ReaderApp.jsx +++ b/static/js/ReaderApp.jsx @@ -1897,86 +1897,6 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { poetryElsToCollapse.forEach(poetryEl => { poetryEl.outerHTML = poetryEl.innerHTML; }); - // function removeClassesFromSpans(element) { - // // Remove classes from the current element - // if (element.tagName === 'SPAN') { - // element.className = ''; - // } - // // Recursively process child elements - // for (let i = 0; i < element.children.length; i++) { - // removeClassesFromSpans(element.children[i]); - // } - // } - // function removeSpansKeepText(element, classListToRemove) { - // // Iterate over the child nodes in reverse order to handle removal - // for (let i = element.children.length - 1; i >= 0; i--) { - // const child = element.children[i]; - // - // // Check if the child is a span - // if (child.tagName === 'SPAN' && classListToRemove.includes(child.className)) { - // // Replace the span with its text content - // const textNode = document.createTextNode(child.textContent); - // element.replaceChild(textNode, child); - // } else { - // // Recursively process non-span child elements - // removeSpansKeepText(child, classListToRemove); - // } - // } - // } - // removeSpansKeepText(container, ["mam-kq-trivial"]); - function removeSpansWithAncestorP(element) { - // Base case: If the current element is a text node, return its text content - if (element.nodeType === Node.TEXT_NODE) { - return element.textContent; - } - - // Check if the current element is a <span> and has a <p> ancestor - if (element.nodeName === 'SPAN' && hasAncestorP(element)) { - // Replace the <span> with its text content - return element.textContent; - } - - // Recursively process child nodes - for (let i = 0; i < element.childNodes.length; i++) { - const childResult = removeSpansWithAncestorP(element.childNodes[i]); - - // Replace the child node with its processed content - if (childResult) { - if (element.childNodes[i].nodeType === Node.TEXT_NODE) { - element.replaceChild(document.createTextNode(childResult), element.childNodes[i]); - } else { - element.replaceChild(createFragmentFromHTML(childResult), element.childNodes[i]); - } - } - } - - // Return the processed content of the current element - return element.outerHTML; - } - - function hasAncestorP(element) { - let ancestor = element.parentNode; - while (ancestor) { - if (ancestor.nodeName === 'P') { - return true; - } - ancestor = ancestor.parentNode; - } - return false; - } - - function createFragmentFromHTML(htmlString) { - const fragment = document.createDocumentFragment(); - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = htmlString; - - while (tempDiv.firstChild) { - fragment.appendChild(tempDiv.firstChild); - } - - return fragment; - } - removeSpansWithAncestorP(container) // Remove extra breaks for continuous mode if (closestReaderPanel && closestReaderPanel.classList.contains('continuous')) { @@ -2017,6 +1937,9 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { let elsToStrip = container.querySelectorAll(linksToStrip); elsToStrip.forEach(el => el.outerHTML = el.innerText); + + let SourceTextSpans = container.querySelectorAll('span:not(.rangeSpan)'); + SourceTextSpans.forEach(el => el.outerHTML = el.innerText); html = container.outerHTML; textOnly = Sefaria.util.htmlToText(html); selectedEls = container; From 92bc445d2490951ef1d660f903d1fbc65590ce0d Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 20 Dec 2023 14:18:18 +0200 Subject: [PATCH 222/260] refactor(translation): new manes for components --- static/js/VersionBlock.jsx | 8 ++++---- static/js/VersionBlockWithPreview.jsx | 8 ++++---- .../{VersionDetailsImage.jsx => VersionImage.jsx} | 6 +++--- ...tailsInformation.jsx => VersionInformation.jsx} | 6 +++--- ...{VersionPreviewMeta.jsx => VersionMetadata.jsx} | 14 +++++++------- ...ewTitleLine.jsx => VersionTitleAndSelector.jsx} | 6 +++--- 6 files changed, 24 insertions(+), 24 deletions(-) rename static/js/{VersionDetailsImage.jsx => VersionImage.jsx} (89%) rename static/js/{VersionDetailsInformation.jsx => VersionInformation.jsx} (95%) rename static/js/{VersionPreviewMeta.jsx => VersionMetadata.jsx} (55%) rename static/js/{VersionBlockWithPreviewTitleLine.jsx => VersionTitleAndSelector.jsx} (87%) diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock.jsx index 969c6cd2c3..e2f1ab7c06 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock.jsx @@ -8,8 +8,8 @@ import Component from 'react-class'; import {LoadingMessage} from "./Misc"; import VersionBlockHeader from "./VersionBlockHeader"; import VersionBlockSelectButton from "./VersionBlockSelectButton"; -import VersionDetailsInformation from "./VersionDetailsInformation"; -import VersionDetailsImage from "./VersionDetailsImage"; +import VersionInformation from "./VersionInformation"; +import VersionImage from "./VersionImage"; import VersionBlockWithPreview from "./VersionBlockWithPreview"; class VersionBlockUtils { @@ -304,8 +304,8 @@ class VersionBlock extends Component { </div> { !v.merged ? <div className="versionDetails sans-serif"> - <VersionDetailsInformation currentRef={this.props.currentRef} version={v}/> - <VersionDetailsImage version={v}/> + <VersionInformation currentRef={this.props.currentRef} version={v}/> + <VersionImage version={v}/> </div> : null } </div> diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlockWithPreview.jsx index 49f744bfa1..b88319e52b 100644 --- a/static/js/VersionBlockWithPreview.jsx +++ b/static/js/VersionBlockWithPreview.jsx @@ -2,8 +2,8 @@ import React, {useEffect, useRef, useState} from 'react'; import PropTypes from 'prop-types'; import VersionBlockHeader from "./VersionBlockHeader"; import {VersionBlockUtils} from './VersionBlock'; -import VersionBlockWithPreviewTitleLine from './VersionBlockWithPreviewTitleLine'; -import VersionPreviewMeta from "./VersionPreviewMeta"; +import VersionTitleAndSelector from './VersionTitleAndSelector'; +import VersionMetadata from "./VersionMetadata"; import {OpenConnectionTabButton} from "./TextList"; function VersionBlockWithPreview({currentRef, version, currObjectVersions, openVersionInSidebar, openVersionInReader, isSelected, srefs, onRangeClick}) { @@ -22,7 +22,7 @@ function VersionBlockWithPreview({currentRef, version, currObjectVersions, openV /> <details> <summary> - <VersionBlockWithPreviewTitleLine + <VersionTitleAndSelector version={version} currentRef={currentRef} currObjectVersions={currObjectVersions} @@ -31,7 +31,7 @@ function VersionBlockWithPreview({currentRef, version, currObjectVersions, openV /> </summary> <div className='version-block-with-preview-details'> - <VersionPreviewMeta + <VersionMetadata currentRef={currentRef} version={version} /> diff --git a/static/js/VersionDetailsImage.jsx b/static/js/VersionImage.jsx similarity index 89% rename from static/js/VersionDetailsImage.jsx rename to static/js/VersionImage.jsx index 0e06692609..1eade3b86e 100644 --- a/static/js/VersionDetailsImage.jsx +++ b/static/js/VersionImage.jsx @@ -4,7 +4,7 @@ import classNames from "classnames"; import Sefaria from "./sefaria/sefaria"; import {VersionBlockUtils} from "./VersionBlock"; -function VersionDetailsImage({version}) { +function VersionImage({version}) { function makeImageLink() { return !!version.purchaseInformationURL ? version.purchaseInformationURL : version.versionSource; } @@ -21,7 +21,7 @@ function VersionDetailsImage({version}) { </div> ) } -VersionDetailsImage.prototypes = { +VersionImage.prototypes = { version: PropTypes.object.isRequired, }; -export default VersionDetailsImage; +export default VersionImage; diff --git a/static/js/VersionDetailsInformation.jsx b/static/js/VersionInformation.jsx similarity index 95% rename from static/js/VersionDetailsInformation.jsx rename to static/js/VersionInformation.jsx index 7814979727..7222fbf8de 100644 --- a/static/js/VersionDetailsInformation.jsx +++ b/static/js/VersionInformation.jsx @@ -4,7 +4,7 @@ import classNames from "classnames"; import Sefaria from "./sefaria/sefaria"; import {VersionBlockUtils} from './VersionBlock'; -function VersionDetailsInformation({currentRef, version}) { +function VersionInformation({currentRef, version}) { function makeLicenseLink() { const license_map = Sefaria.getLicenseMap(); return (version.license in license_map) ? license_map[version.license] : "#"; @@ -48,8 +48,8 @@ function VersionDetailsInformation({currentRef, version}) { </div> ); } -VersionDetailsInformation.prototypes = { +VersionInformation.prototypes = { currentRef: PropTypes.string.isRequired, version: PropTypes.object.isRequired, }; -export default VersionDetailsInformation; +export default VersionInformation; diff --git a/static/js/VersionPreviewMeta.jsx b/static/js/VersionMetadata.jsx similarity index 55% rename from static/js/VersionPreviewMeta.jsx rename to static/js/VersionMetadata.jsx index 76e0c1d2e1..2226e4307f 100644 --- a/static/js/VersionPreviewMeta.jsx +++ b/static/js/VersionMetadata.jsx @@ -1,22 +1,22 @@ import React from 'react'; import PropTypes from 'prop-types'; import {VersionBlockUtils} from "./VersionBlock"; -import VersionDetailsInformation from "./VersionDetailsInformation"; -import VersionDetailsImage from "./VersionDetailsImage"; +import VersionInformation from "./VersionInformation"; +import VersionImage from "./VersionImage"; -function VersionPreviewMeta({currentRef, version}) { +function VersionMetadata({currentRef, version}) { return ( <div className='versionDetails preview'> <div className='version-preview-informations'> <div className='versionDetails-version-title'>{VersionBlockUtils.makeVersionTitle(version).text}</div> - <VersionDetailsInformation currentRef={currentRef} version={version}/> + <VersionInformation currentRef={currentRef} version={version}/> </div> - <VersionDetailsImage version={version}/> + <VersionImage version={version}/> </div> ); } -VersionPreviewMeta.prototypes = { +VersionMetadata.prototypes = { currentRef: PropTypes.string.isRequired, version: PropTypes.object.isRequired, }; -export default VersionPreviewMeta; +export default VersionMetadata; diff --git a/static/js/VersionBlockWithPreviewTitleLine.jsx b/static/js/VersionTitleAndSelector.jsx similarity index 87% rename from static/js/VersionBlockWithPreviewTitleLine.jsx rename to static/js/VersionTitleAndSelector.jsx index 9d39ee7935..907888c5ca 100644 --- a/static/js/VersionBlockWithPreviewTitleLine.jsx +++ b/static/js/VersionTitleAndSelector.jsx @@ -4,7 +4,7 @@ import VersionBlockSelectButton from "./VersionBlockSelectButton"; import {VersionBlockUtils} from './VersionBlock'; import Sefaria from "./sefaria/sefaria"; -function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersions, openVersionInReader, isSelected}) { +function VersionTitleAndSelector({currentRef, version, currObjectVersions, openVersionInReader, isSelected}) { function makeShortVersionTitle() { let shortVersionTitle = version.shortVersionTitle || version.versionTitle; if (Sefaria.interfaceLang === "hebrew") { @@ -29,11 +29,11 @@ function VersionBlockWithPreviewTitleLine({currentRef, version, currObjectVersio </div> ); } -VersionBlockWithPreviewTitleLine.prototypes = { +VersionTitleAndSelector.prototypes = { currObjectVersions: PropTypes.object.isRequired, version: PropTypes.object.isRequired, currentRef: PropTypes.string.isRequired, openVersionInReader: PropTypes.func.isRequired, isSelected: PropTypes.bool.isRequired, }; -export default VersionBlockWithPreviewTitleLine; +export default VersionTitleAndSelector; From 79140ad8da8505611752c1323460cb626053c5b4 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Wed, 20 Dec 2023 14:23:39 +0200 Subject: [PATCH 223/260] refactor(translation): move all VersionBlock related components to a new folder --- static/js/AboutBox.jsx | 2 +- static/js/BookPage.jsx | 2 +- static/js/TranslationsBox.jsx | 2 +- static/js/{ => VersionBlock}/VersionBlock.jsx | 8 ++++---- static/js/{ => VersionBlock}/VersionBlockHeader.jsx | 0 static/js/{ => VersionBlock}/VersionBlockSelectButton.jsx | 0 static/js/{ => VersionBlock}/VersionBlockWithPreview.jsx | 2 +- static/js/{ => VersionBlock}/VersionImage.jsx | 2 +- static/js/{ => VersionBlock}/VersionInformation.jsx | 2 +- static/js/{ => VersionBlock}/VersionMetadata.jsx | 0 static/js/{ => VersionBlock}/VersionTitleAndSelector.jsx | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) rename static/js/{ => VersionBlock}/VersionBlock.jsx (99%) rename static/js/{ => VersionBlock}/VersionBlockHeader.jsx (100%) rename static/js/{ => VersionBlock}/VersionBlockSelectButton.jsx (100%) rename static/js/{ => VersionBlock}/VersionBlockWithPreview.jsx (97%) rename static/js/{ => VersionBlock}/VersionImage.jsx (96%) rename static/js/{ => VersionBlock}/VersionInformation.jsx (98%) rename static/js/{ => VersionBlock}/VersionMetadata.jsx (100%) rename static/js/{ => VersionBlock}/VersionTitleAndSelector.jsx (97%) diff --git a/static/js/AboutBox.jsx b/static/js/AboutBox.jsx index 2d7435a170..292ce5896c 100644 --- a/static/js/AboutBox.jsx +++ b/static/js/AboutBox.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Sefaria from './sefaria/sefaria'; -import VersionBlock, {VersionsBlocksList} from './VersionBlock'; +import VersionBlock, {VersionsBlocksList} from './VersionBlock/VersionBlock'; import Component from 'react-class'; import {InterfaceText} from "./Misc"; import {ContentText} from "./ContentText"; diff --git a/static/js/BookPage.jsx b/static/js/BookPage.jsx index 6f21dc9187..5edd222026 100644 --- a/static/js/BookPage.jsx +++ b/static/js/BookPage.jsx @@ -21,7 +21,7 @@ import $ from './sefaria/sefariaJquery'; import Sefaria from './sefaria/sefaria'; import { NavSidebar, Modules } from './NavSidebar'; import DictionarySearch from './DictionarySearch'; -import VersionBlock from './VersionBlock'; +import VersionBlock from './VersionBlock/VersionBlock'; import ExtendedNotes from './ExtendedNotes'; import Footer from './Footer'; import classNames from 'classnames'; diff --git a/static/js/TranslationsBox.jsx b/static/js/TranslationsBox.jsx index 31bf7d8b5d..117145f759 100644 --- a/static/js/TranslationsBox.jsx +++ b/static/js/TranslationsBox.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Sefaria from './sefaria/sefaria'; -import {VersionsBlocksList} from './VersionBlock'; +import {VersionsBlocksList} from './VersionBlock/VersionBlock'; import Component from 'react-class'; import {EnglishText, HebrewText, InterfaceText, LoadingMessage} from "./Misc"; import {RecentFilterSet} from "./ConnectionFilters"; diff --git a/static/js/VersionBlock.jsx b/static/js/VersionBlock/VersionBlock.jsx similarity index 99% rename from static/js/VersionBlock.jsx rename to static/js/VersionBlock/VersionBlock.jsx index e2f1ab7c06..4cf6828bba 100644 --- a/static/js/VersionBlock.jsx +++ b/static/js/VersionBlock/VersionBlock.jsx @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import Sefaria from './sefaria/sefaria'; -import Util from './sefaria/util'; -import $ from './sefaria/sefariaJquery'; +import Sefaria from '../sefaria/sefaria'; +import Util from '../sefaria/util'; +import $ from '../sefaria/sefariaJquery'; import Component from 'react-class'; -import {LoadingMessage} from "./Misc"; +import {LoadingMessage} from "../Misc"; import VersionBlockHeader from "./VersionBlockHeader"; import VersionBlockSelectButton from "./VersionBlockSelectButton"; import VersionInformation from "./VersionInformation"; diff --git a/static/js/VersionBlockHeader.jsx b/static/js/VersionBlock/VersionBlockHeader.jsx similarity index 100% rename from static/js/VersionBlockHeader.jsx rename to static/js/VersionBlock/VersionBlockHeader.jsx diff --git a/static/js/VersionBlockSelectButton.jsx b/static/js/VersionBlock/VersionBlockSelectButton.jsx similarity index 100% rename from static/js/VersionBlockSelectButton.jsx rename to static/js/VersionBlock/VersionBlockSelectButton.jsx diff --git a/static/js/VersionBlockWithPreview.jsx b/static/js/VersionBlock/VersionBlockWithPreview.jsx similarity index 97% rename from static/js/VersionBlockWithPreview.jsx rename to static/js/VersionBlock/VersionBlockWithPreview.jsx index b88319e52b..800880a737 100644 --- a/static/js/VersionBlockWithPreview.jsx +++ b/static/js/VersionBlock/VersionBlockWithPreview.jsx @@ -4,7 +4,7 @@ import VersionBlockHeader from "./VersionBlockHeader"; import {VersionBlockUtils} from './VersionBlock'; import VersionTitleAndSelector from './VersionTitleAndSelector'; import VersionMetadata from "./VersionMetadata"; -import {OpenConnectionTabButton} from "./TextList"; +import {OpenConnectionTabButton} from "../TextList"; function VersionBlockWithPreview({currentRef, version, currObjectVersions, openVersionInSidebar, openVersionInReader, isSelected, srefs, onRangeClick}) { const opeInSidebar = VersionBlockUtils.openVersionInSidebar.bind(null, currentRef, version, currObjectVersions, openVersionInSidebar); diff --git a/static/js/VersionImage.jsx b/static/js/VersionBlock/VersionImage.jsx similarity index 96% rename from static/js/VersionImage.jsx rename to static/js/VersionBlock/VersionImage.jsx index 1eade3b86e..33169df196 100644 --- a/static/js/VersionImage.jsx +++ b/static/js/VersionBlock/VersionImage.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from "classnames"; -import Sefaria from "./sefaria/sefaria"; +import Sefaria from "../sefaria/sefaria"; import {VersionBlockUtils} from "./VersionBlock"; function VersionImage({version}) { diff --git a/static/js/VersionInformation.jsx b/static/js/VersionBlock/VersionInformation.jsx similarity index 98% rename from static/js/VersionInformation.jsx rename to static/js/VersionBlock/VersionInformation.jsx index 7222fbf8de..6a48ed8ccf 100644 --- a/static/js/VersionInformation.jsx +++ b/static/js/VersionBlock/VersionInformation.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from "classnames"; -import Sefaria from "./sefaria/sefaria"; +import Sefaria from "../sefaria/sefaria"; import {VersionBlockUtils} from './VersionBlock'; function VersionInformation({currentRef, version}) { diff --git a/static/js/VersionMetadata.jsx b/static/js/VersionBlock/VersionMetadata.jsx similarity index 100% rename from static/js/VersionMetadata.jsx rename to static/js/VersionBlock/VersionMetadata.jsx diff --git a/static/js/VersionTitleAndSelector.jsx b/static/js/VersionBlock/VersionTitleAndSelector.jsx similarity index 97% rename from static/js/VersionTitleAndSelector.jsx rename to static/js/VersionBlock/VersionTitleAndSelector.jsx index 907888c5ca..2bcfb66028 100644 --- a/static/js/VersionTitleAndSelector.jsx +++ b/static/js/VersionBlock/VersionTitleAndSelector.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import VersionBlockSelectButton from "./VersionBlockSelectButton"; import {VersionBlockUtils} from './VersionBlock'; -import Sefaria from "./sefaria/sefaria"; +import Sefaria from "../sefaria/sefaria"; function VersionTitleAndSelector({currentRef, version, currObjectVersions, openVersionInReader, isSelected}) { function makeShortVersionTitle() { From 8be051bc03f315931422e7b5011a5bbc3ce09cdc Mon Sep 17 00:00:00 2001 From: yonadavGit <yonadav.le@gmail.com> Date: Wed, 20 Dec 2023 15:31:23 +0200 Subject: [PATCH 224/260] fix(copyFromReader): clean code and remove debug prints --- static/js/ReaderApp.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/static/js/ReaderApp.jsx b/static/js/ReaderApp.jsx index 8f55710f34..abd9c3f6fc 100644 --- a/static/js/ReaderApp.jsx +++ b/static/js/ReaderApp.jsx @@ -1938,14 +1938,13 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { elsToStrip.forEach(el => el.outerHTML = el.innerText); + // Collapse all spans that are not rangeSpan. This is also needed for specifically pasting into Google Docs in Chrome to work. let SourceTextSpans = container.querySelectorAll('span:not(.rangeSpan)'); SourceTextSpans.forEach(el => el.outerHTML = el.innerText); + html = container.outerHTML; textOnly = Sefaria.util.htmlToText(html); selectedEls = container; - console.log(container) - console.log(html) - console.log(textOnly) } From c5854993514318c7e955e53620fc82c86fd04078 Mon Sep 17 00:00:00 2001 From: yonadavGit <yonadav.le@gmail.com> Date: Thu, 21 Dec 2023 12:39:03 +0200 Subject: [PATCH 225/260] feat(AboutNavBar): add hebrew getting started video button --- static/js/NavSidebar.jsx | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/static/js/NavSidebar.jsx b/static/js/NavSidebar.jsx index 69976798fe..d3f9ce52cf 100644 --- a/static/js/NavSidebar.jsx +++ b/static/js/NavSidebar.jsx @@ -112,13 +112,24 @@ const AboutSefaria = ({hideTitle}) => ( <HebrewText>לקריאה נוספת ›</HebrewText> </InterfaceText> </a> - { Sefaria.interfaceLang === 'english' && !hideTitle && - <a className="button get-start" href="/sheets/210670"> - <img src="/static/icons/vector.svg"/> - <div className="get-start"> - Getting Started (2 min) - </div> - </a> + {!hideTitle && <InterfaceText> + <EnglishText> + <a className="button get-start" href="/sheets/210670"> + <img src="/static/icons/vector.svg"/> + <div className="get-start"> + Getting Started (2 min) + </div> + </a> + </EnglishText> + <HebrewText> + <a className="button get-start" href="https://youtu.be/eo5e8Z54h1Y"> + <img src="/static/icons/vector.svg"/> + <div className="get-start"> + הכירו את ספריא + </div> + </a> + </HebrewText> + </InterfaceText> } </Module> ); From 96b2f03d6bc784f1e4da4dfe1a9e7873a724de87 Mon Sep 17 00:00:00 2001 From: yonadavGit <yonadav.le@gmail.com> Date: Thu, 21 Dec 2023 12:55:05 +0200 Subject: [PATCH 226/260] feat(AboutNavBar): rotate vector img --- static/icons/vector-turning-left.svg | 5 +++++ static/js/NavSidebar.jsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 static/icons/vector-turning-left.svg diff --git a/static/icons/vector-turning-left.svg b/static/icons/vector-turning-left.svg new file mode 100644 index 0000000000..136c4325a1 --- /dev/null +++ b/static/icons/vector-turning-left.svg @@ -0,0 +1,5 @@ +<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"> + <g transform="rotate(-180 9 9.5)"> + <path d="M9 2C13.1355 2 16.5 5.3645 16.5 9.5C16.5 13.6355 13.1355 17 9 17C4.8645 17 1.5 13.6355 1.5 9.5C1.5 5.3645 4.8645 2 9 2ZM9 0.5C4.02975 0.5 0 4.52975 0 9.5C0 14.4703 4.02975 18.5 9 18.5C13.9703 18.5 18 14.4703 18 9.5C18 4.52975 13.9703 0.5 9 0.5ZM6.75 13.25V5.75L13.5 9.6095L6.75 13.25Z" fill="white"/> + </g> +</svg> diff --git a/static/js/NavSidebar.jsx b/static/js/NavSidebar.jsx index d3f9ce52cf..61a1f5c3d1 100644 --- a/static/js/NavSidebar.jsx +++ b/static/js/NavSidebar.jsx @@ -123,7 +123,7 @@ const AboutSefaria = ({hideTitle}) => ( </EnglishText> <HebrewText> <a className="button get-start" href="https://youtu.be/eo5e8Z54h1Y"> - <img src="/static/icons/vector.svg"/> + <img src="/static/icons/vector-turning-left.svg"/> <div className="get-start"> הכירו את ספריא </div> From abdd9f4a8f22c82ba4a7bddfcc8c56fc9cf78974 Mon Sep 17 00:00:00 2001 From: yonadavGit <yonadav.le@gmail.com> Date: Thu, 21 Dec 2023 13:27:49 +0200 Subject: [PATCH 227/260] feat(AboutNavBar): add video length to button text --- static/js/NavSidebar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/NavSidebar.jsx b/static/js/NavSidebar.jsx index 61a1f5c3d1..cfbf20b8a0 100644 --- a/static/js/NavSidebar.jsx +++ b/static/js/NavSidebar.jsx @@ -125,7 +125,7 @@ const AboutSefaria = ({hideTitle}) => ( <a className="button get-start" href="https://youtu.be/eo5e8Z54h1Y"> <img src="/static/icons/vector-turning-left.svg"/> <div className="get-start"> - הכירו את ספריא + הכירו את ספריא (2 דק') </div> </a> </HebrewText> From fe4bb0b49933e5ceb11d9ce6cf1b538a8122886b Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 24 Dec 2023 11:45:18 +0200 Subject: [PATCH 228/260] doc(api-v3): doc strings for Version attributes. --- sefaria/model/text.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 22c2e19199..26e486144b 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1334,12 +1334,12 @@ class Version(AbstractTextRecord, abst.AbstractMongoRecord, AbstractSchemaConten "purchaseInformationImage", "purchaseInformationURL", "hasManuallyWrappedRefs", # true for texts where refs were manually wrapped in a-tags. no need to run linker at run-time. - "actualLanguage", - 'languageFamilyName', - "isBaseText", - 'isSource', - 'isPrimary', - 'direction', # 'rtl' or 'ltr' + "actualLanguage", # ISO language code + 'languageFamilyName', # full name of the language, but without specificity (for Judeo Arabic actualLanguage=jrb, languageFamilyName=arabic + "isBaseText", # should be deprecated (needs some changes on client side) + 'isSource', # bool, True if this version is not a translation + 'isPrimary', # bool, True if we see it as a primary version (usually equals to isSource, but Hebrew Kuzarif or example is primary but not source) + 'direction', # 'rtl' or 'ltr' ] def __str__(self): From 4e482c0708818c516d21d7526a4eda1f3210df74 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 24 Dec 2023 11:51:00 +0200 Subject: [PATCH 229/260] fix(api-v3): change 400 to 404 when ref is invalid or empty. --- api/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/views.py b/api/views.py index 982818902c..05e3b6c1d8 100644 --- a/api/views.py +++ b/api/views.py @@ -13,7 +13,7 @@ def dispatch(self, request, *args, **kwargs): try: self.oref = Ref.instantiate_ref_with_legacy_parse_fallback(kwargs['tref']) except Exception as e: - return jsonResponse({'error': getattr(e, 'message', str(e))}, status=400) + return jsonResponse({'error': getattr(e, 'message', str(e))}, status=404) return super().dispatch(request, *args, **kwargs) @staticmethod @@ -43,7 +43,7 @@ def _handle_warnings(self, data): def get(self, request, *args, **kwargs): if self.oref.is_empty() and not self.oref.index_node.is_virtual: - return jsonResponse({'error': f'We have no text for {self.oref}.'}, status=400) + return jsonResponse({'error': f'We have no text for {self.oref}.'}, status=404) versions_params = request.GET.getlist('version', []) if not versions_params: versions_params = ['primary'] From 201d8af85cb53e9e3aebee30373b530d4bd3af41 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 24 Dec 2023 11:53:50 +0200 Subject: [PATCH 230/260] refactor(api-v3): change TextManger to TextRequestAdapter. --- api/views.py | 4 ++-- sefaria/model/{text_manager.py => text_reuqest_adapter.py} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename sefaria/model/{text_manager.py => text_reuqest_adapter.py} (99%) diff --git a/api/views.py b/api/views.py index 05e3b6c1d8..0e714da047 100644 --- a/api/views.py +++ b/api/views.py @@ -1,5 +1,5 @@ from sefaria.model import * -from sefaria.model.text_manager import TextManager +from sefaria.model.text_reuqest_adapter import TextRequestAdapter from sefaria.client.util import jsonResponse from django.views import View from .api_warnings import * @@ -52,7 +52,7 @@ def get(self, request, *args, **kwargs): return_format = request.GET.get('return_format', 'default') if return_format not in self.RETURN_FORMATS: return jsonResponse({'error': f'return_format should be one of those formats: {self.RETURN_FORMATS}.'}, status=400) - text_manager = TextManager(self.oref, versions_params, fill_in_missing_segments, return_format) + text_manager = TextRequestAdapter(self.oref, versions_params, fill_in_missing_segments, return_format) data = text_manager.get_versions_for_query() data = self._handle_warnings(data) return jsonResponse(data) diff --git a/sefaria/model/text_manager.py b/sefaria/model/text_reuqest_adapter.py similarity index 99% rename from sefaria/model/text_manager.py rename to sefaria/model/text_reuqest_adapter.py index f14a54bac8..e6963cf348 100644 --- a/sefaria/model/text_manager.py +++ b/sefaria/model/text_reuqest_adapter.py @@ -9,7 +9,7 @@ from sefaria.system.exceptions import InputError from sefaria.datatype.jagged_array import JaggedTextArray -class TextManager: +class TextRequestAdapter: ALL = 'all' PRIMARY = 'primary' SOURCE = 'source' From 2943990b858b5eb57776418684d07cbaa2fd481e Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 24 Dec 2023 11:58:02 +0200 Subject: [PATCH 231/260] doc(api-v3): doc string for TextRequestAdapter. --- sefaria/model/text_reuqest_adapter.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sefaria/model/text_reuqest_adapter.py b/sefaria/model/text_reuqest_adapter.py index e6963cf348..1bae459909 100644 --- a/sefaria/model/text_reuqest_adapter.py +++ b/sefaria/model/text_reuqest_adapter.py @@ -10,6 +10,11 @@ from sefaria.datatype.jagged_array import JaggedTextArray class TextRequestAdapter: + """ + This class is used for getting texts for client side (API or SSR) + It takes the same params as the api/v3/text (ref, version_params that are language and versionTitle, fill_in_missing_segments, and return_format + It returns a JSON-like object for an HTTP response. + """ ALL = 'all' PRIMARY = 'primary' SOURCE = 'source' From de5e59debbb065a6c5462275f684795e378708c9 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 24 Dec 2023 12:05:42 +0200 Subject: [PATCH 232/260] doc(api-v3): doc string for TextRange. --- sefaria/model/text.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 26e486144b..56d596000c 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1696,6 +1696,11 @@ def __call__(cls, *args, **kwargs): class TextRange: + """ + This class is planned to replace TextChunk, using real language rather than he/en + For now it's used by v3 texts api + It can be used for getting text, but not yet for saving + """ def __init__(self, oref, lang, vtitle, merge_versions=False): if isinstance(oref.index_node, JaggedArrayNode) or isinstance(oref.index_node, DictionaryEntryNode): #text cannot be SchemaNode From 01eb7d1067e4e58c58f4f5a5b30ea18fe2804d81 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 24 Dec 2023 15:20:33 +0200 Subject: [PATCH 233/260] refactor(api-v3): change versions to be a private param with a public setter. --- sefaria/model/text.py | 25 +++++++++++-------------- sefaria/model/text_reuqest_adapter.py | 4 ++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 56d596000c..e72d3728da 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1700,9 +1700,10 @@ class TextRange: This class is planned to replace TextChunk, using real language rather than he/en For now it's used by v3 texts api It can be used for getting text, but not yet for saving + The versions param is for better performance when the version(s) were already loaded from mongo """ - def __init__(self, oref, lang, vtitle, merge_versions=False): + def __init__(self, oref, lang, vtitle, merge_versions=False, versions=None): if isinstance(oref.index_node, JaggedArrayNode) or isinstance(oref.index_node, DictionaryEntryNode): #text cannot be SchemaNode self.oref = oref elif oref.has_default_child(): #use default child: @@ -1712,22 +1713,18 @@ def __init__(self, oref, lang, vtitle, merge_versions=False): self.lang = lang self.vtitle = vtitle self.merge_versions = merge_versions - self._versions = [] self._text = None self.sources = None + self._set_versions(versions) - @property - def versions(self): - if self._versions == []: + def _set_versions(self, versions): + if versions: + self._validate_versions(versions) + self._versions = versions + else: condition_query = self.oref.condition_query(self.lang) if self.merge_versions else \ {'title': self.oref.index.title, 'languageFamilyName': self.lang, 'versionTitle': self.vtitle} self._versions = VersionSet(condition_query, proj=self.oref.part_projection()) - return self._versions - - @versions.setter - def versions(self, versions): - self._validate_versions(versions) - self._versions = versions def _validate_versions(self, versions): if not self.merge_versions and len(versions) > 1: @@ -1758,15 +1755,15 @@ def _trim_text(self, text): @property def text(self): if self._text is None: - if self.merge_versions and len(self.versions) > 1: - merged_text, sources = self.versions.merge(self.oref.index_node, prioritized_vtitle=self.vtitle) + if self.merge_versions and len(self._versions) > 1: + merged_text, sources = self._versions.merge(self.oref.index_node, prioritized_vtitle=self.vtitle) self._text = self._trim_text(merged_text) if len(sources) > 1: self.sources = sources elif self.oref.index_node.is_virtual: self._text = self.oref.index_node.get_text() else: - self._text = self._trim_text(self.versions[0].content_node(self.oref.index_node)) #todo if there is no version it will fail + self._text = self._trim_text(self._versions[0].content_node(self.oref.index_node)) #todo if there is no version it will fail return self._text diff --git a/sefaria/model/text_reuqest_adapter.py b/sefaria/model/text_reuqest_adapter.py index 1bae459909..e47e5fd047 100644 --- a/sefaria/model/text_reuqest_adapter.py +++ b/sefaria/model/text_reuqest_adapter.py @@ -43,7 +43,6 @@ def _append_version(self, version): for attr in ['chapter', 'title']: fields.remove(attr) version_details = {f: getattr(version, f, "") for f in fields} - text_range = TextRange(self.oref, version.languageFamilyName, version.versionTitle, self.fill_in_missing_segments) if self.fill_in_missing_segments: # we need a new VersionSet of only the relevant versions for merging. copy should be better than calling for mongo @@ -51,7 +50,8 @@ def _append_version(self, version): relevant_versions.remove(lambda v: v.languageFamilyName != version.languageFamilyName) else: relevant_versions = [version] - text_range.versions = relevant_versions + text_range = TextRange(self.oref, version.languageFamilyName, version.versionTitle, + self.fill_in_missing_segments, relevant_versions) version_details['text'] = text_range.text sources = getattr(text_range, 'sources', None) From fc8ff4c974566baa604fb70962fc9eca7055c066 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 24 Dec 2023 15:21:11 +0200 Subject: [PATCH 234/260] test(api-v3): fix tests to fit the 400-404 change. --- api/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/tests.py b/api/tests.py index 76709e1612..c3b0ffefba 100644 --- a/api/tests.py +++ b/api/tests.py @@ -119,7 +119,7 @@ def text_api_virtual_node(self): def test_api_get_text_bad_text(self): response = c.get('/api/v3/texts/Life_of_Pi.13.13') - self.assertEqual(400, response.status_code) + self.assertEqual(404, response.status_code) data = json.loads(response.content) self.assertEqual(data["error"], "Could not find title in reference: Life of Pi.13.13") @@ -135,13 +135,13 @@ def test_api_get_text_too_many_hyphens(self): def test_api_get_text_bad_sections(self): response = c.get('/api/v3/texts/Job.6-X') - self.assertEqual(400, response.status_code) + self.assertEqual(404, response.status_code) data = json.loads(response.content) self.assertEqual(data["error"], "Couldn't understand text sections: 'Job.6-X'.") def test_api_get_text_empty_ref(self): response = c.get("/api/v3/texts/Berakhot.1a") - self.assertEqual(400, response.status_code) + self.assertEqual(404, response.status_code) data = json.loads(response.content) self.assertEqual(data["error"], "We have no text for Berakhot 1a.") From 5f3fa2402e64d28fd5e91e0d810569f02c46d737 Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Sun, 24 Dec 2023 16:11:30 +0200 Subject: [PATCH 235/260] refactor(api-v3): change isBaseText on client side to isSource (next step is deprecating it). --- static/js/VersionBlock/VersionBlock.jsx | 2 +- static/js/sefaria/sefaria.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/VersionBlock/VersionBlock.jsx b/static/js/VersionBlock/VersionBlock.jsx index 4cf6828bba..36a33099f2 100644 --- a/static/js/VersionBlock/VersionBlock.jsx +++ b/static/js/VersionBlock/VersionBlock.jsx @@ -187,7 +187,7 @@ class VersionBlock extends Component { } } makeSelectVersionLanguage(){ - let voc = this.props.version.isBaseText ? 'Version' : "Translation"; + let voc = this.props.version.isSource ? 'Version' : "Translation"; return this.props.isCurrent ? Sefaria._("Current " + voc) : Sefaria._("Select "+ voc); } diff --git a/static/js/sefaria/sefaria.js b/static/js/sefaria/sefaria.js index 16d4e456dd..a39d3b2566 100644 --- a/static/js/sefaria/sefaria.js +++ b/static/js/sefaria/sefaria.js @@ -692,13 +692,13 @@ Sefaria = extend(Sefaria, { }, getTranslations: async function(ref) { /** - * Gets all versions except Hebrew versions that have isBaseText true + * Gets all versions except Hebrew versions that have isSource true * @ref {string} ref * @returns {string: [versions]} Versions by language */ return Sefaria.getVersions(ref).then(result => { let versions = Sefaria.filterVersionsObjByLangs(result, ['he'], false); - let heVersions = Sefaria.filterVersionsArrayByAttr(result?.he || [], {isBaseText: false}); + let heVersions = Sefaria.filterVersionsArrayByAttr(result?.he || [], {isSource: false}); if (heVersions.length) { versions.he = heVersions; } From d6552834e7e905b1407ca025796d842824261688 Mon Sep 17 00:00:00 2001 From: yonadavGit <yonadav.le@gmail.com> Date: Mon, 25 Dec 2023 16:01:32 +0200 Subject: [PATCH 236/260] fix(SearchFilters): changed 'path' regex to ignore books whose title is the prefix of the current book --- sefaria/helper/search.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sefaria/helper/search.py b/sefaria/helper/search.py index 757c931bfa..833c023b03 100644 --- a/sefaria/helper/search.py +++ b/sefaria/helper/search.py @@ -140,7 +140,9 @@ def get_filter_obj(type, filters, filter_fields): def make_filter(type, agg_type, agg_key): if type == "text": # filters with '/' might be leading to books. also, very unlikely they'll match an false positives - reg = re.escape(agg_key) + (".*" if "/" in agg_key else "/.*") + agg_key = agg_key.rstrip('/') + agg_key = re.escape(agg_key) + reg = f"{agg_key}|{agg_key}/.*" return Regexp(path=reg) elif type == "sheet": return Term(**{agg_type: agg_key}) From d6240795ff9a1c025eb1daf656bcc70197ffca3b Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Tue, 26 Dec 2023 09:02:10 +0200 Subject: [PATCH 237/260] fix(translations): hide pseudo-element marker (for safari doesn't support content for it) and use instead the before pseudo-element. --- static/css/s2.css | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/static/css/s2.css b/static/css/s2.css index d5dfb23712..c097236932 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3567,12 +3567,20 @@ display: none; [open] .versionBlock.with-preview summary { margin-bottom: 10px; } -.versionBlock.with-preview summary::marker { +.versionBlock.with-preview details summary::before { content: url('/static/icons/little-chevron-down.svg'); } -[open] .versionBlock.with-preview summary::marker { +.versionBlock.with-preview details[open] summary::before { content: url('/static/icons/little-chevron-up.svg'); } +.versionBlock.with-preview summary::-webkit-details-marker { + /*hide marker for safari*/ + display: none; +} +.versionBlock.with-preview summary { + /*hide marker for chrome*/ + list-style: none; +} details .open-details::before { margin-inline-end: 5px; } From ef0644a97e3a3ccf2f1b03df86b3a81bb02606cd Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Tue, 26 Dec 2023 09:03:18 +0200 Subject: [PATCH 238/260] fix(translations): pointer cursor for all summary element. --- static/css/s2.css | 1 + 1 file changed, 1 insertion(+) diff --git a/static/css/s2.css b/static/css/s2.css index c097236932..a53431c3f8 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -3580,6 +3580,7 @@ display: none; .versionBlock.with-preview summary { /*hide marker for chrome*/ list-style: none; + cursor: pointer; } details .open-details::before { margin-inline-end: 5px; From 0f6a3b3de4213e8d64c4de8fd45d2cc5e91b06eb Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Tue, 26 Dec 2023 13:12:39 +0200 Subject: [PATCH 239/260] fix(Topic Editor): all topics can have alt titles --- static/js/TopicEditor.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/js/TopicEditor.jsx b/static/js/TopicEditor.jsx index 0c36f2489c..a916a71685 100644 --- a/static/js/TopicEditor.jsx +++ b/static/js/TopicEditor.jsx @@ -191,13 +191,13 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => { const url = `/api/topic/delete/${data.origSlug}`; requestWithCallBack({url, type: "DELETE", redirect: () => window.location.href = "/topics"}); } - let items = ["Title", "Hebrew Title", "English Description", "Hebrew Description", "Category Menu"]; + let items = ["Title", "Hebrew Title", "English Description", "Hebrew Description", "Category Menu", "English Alternate Titles", "Hebrew Alternate Titles",]; if (isCategory) { items.push("English Short Description"); items.push("Hebrew Short Description"); } if (isAuthor) { - const authorItems = ["English Alternate Titles", "Hebrew Alternate Titles", "Birth Place", "Hebrew Birth Place", "Birth Year", "Place of Death", "Hebrew Place of Death", "Death Year", "Era"]; + const authorItems = ["Birth Place", "Hebrew Birth Place", "Birth Year", "Place of Death", "Hebrew Place of Death", "Death Year", "Era"]; authorItems.forEach(x => items.push(x)); } items.push("Picture Uploader"); From 43647489c1a72c8feb92aaf8c9a163a8e5de2008 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Tue, 26 Dec 2023 14:06:22 +0200 Subject: [PATCH 240/260] fix(Topics): display without links in the TOC but with links on topic pages --- static/js/Misc.jsx | 4 ++-- static/js/TopicPage.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 4d7205fc74..d5fab55bbc 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -60,7 +60,7 @@ const __filterChildrenByLanguage = (children, language) => { return newChildren; }; -const InterfaceText = ({text, html, markdown, children, context}) => { +const InterfaceText = ({text, html, markdown, children, context, disallowedMarkdownElements=[]}) => { /** * Renders a single span for interface string with either class `int-en`` or `int-he` depending on Sefaria.interfaceLang. * If passed explicit text or html objects as props with "en" and/or "he", will only use those to determine correct text or fallback text to display. @@ -92,7 +92,7 @@ const InterfaceText = ({text, html, markdown, children, context}) => { return ( html ? <span className={elemclasses} dangerouslySetInnerHTML={{__html: textResponse}}/> - : markdown ? <span className={elemclasses}><ReactMarkdown className={'reactMarkdown'} unwrapDisallowed={true} disallowedElements={['p']}>{textResponse}</ReactMarkdown></span> + : markdown ? <span className={elemclasses}><ReactMarkdown className={'reactMarkdown'} unwrapDisallowed={true} disallowedElements={['p', ...disallowedMarkdownElements]}>{textResponse}</ReactMarkdown></span> : <span className={elemclasses}>{textResponse}</span> ); }; diff --git a/static/js/TopicPage.jsx b/static/js/TopicPage.jsx index 5d767d83a2..70fa05f305 100644 --- a/static/js/TopicPage.jsx +++ b/static/js/TopicPage.jsx @@ -231,7 +231,7 @@ const TopicCategory = ({topic, topicTitle, setTopic, setNavTopic, compare, initi </a> {description ? <div className="navBlockDescription clamped"> - <InterfaceText markdown={{en: description.en, he: description.he}} /> + <InterfaceText markdown={{en: description.en, he: description.he}} disallowedMarkdownElements={['a']}/> </div> : null } </div> From 1316f7e16337a1e9d28a95863a8b6822ca8df6f7 Mon Sep 17 00:00:00 2001 From: yonadavGit <yonadav.le@gmail.com> Date: Tue, 26 Dec 2023 15:34:44 +0200 Subject: [PATCH 241/260] chore(AboutNavSideBar): update link to video --- static/js/NavSidebar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/NavSidebar.jsx b/static/js/NavSidebar.jsx index cfbf20b8a0..dfbaedae0e 100644 --- a/static/js/NavSidebar.jsx +++ b/static/js/NavSidebar.jsx @@ -122,7 +122,7 @@ const AboutSefaria = ({hideTitle}) => ( </a> </EnglishText> <HebrewText> - <a className="button get-start" href="https://youtu.be/eo5e8Z54h1Y"> + <a className="button get-start" href="https://youtu.be/rCADxtqPqnw"> <img src="/static/icons/vector-turning-left.svg"/> <div className="get-start"> הכירו את ספריא (2 דק') From 5c62082694e5c72e057ebb1ab67b3a656afd8da8 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Thu, 28 Dec 2023 11:31:05 +0200 Subject: [PATCH 242/260] fix(Topics): topics shown in sidebar with markdown did not display markdown properly --- static/js/ContentText.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/static/js/ContentText.jsx b/static/js/ContentText.jsx index ed0ab120b7..234979da76 100644 --- a/static/js/ContentText.jsx +++ b/static/js/ContentText.jsx @@ -1,6 +1,7 @@ import React from "react"; import {useContentLang} from './Hooks'; import Sefaria from './sefaria/sefaria'; +import ReactMarkdown from "react-markdown"; const ContentText = (props) => { /* Renders content language throughout the site (content that comes from the database and is not interface language). @@ -14,7 +15,7 @@ const ContentText = (props) => { * order to return the bilingual langauage elements in (as opposed to the unguaranteed order by default). */ const langAndContentItems = _filterContentTextByLang(props); - return langAndContentItems.map(item => <ContentSpan lang={item[0]} content={item[1]} isHTML={!!props.html}/>); + return langAndContentItems.map(item => <ContentSpan lang={item[0]} content={item[1]} isHTML={!!props.html} markdown={props.markdown}/>); }; const VersionContent = (props) => { @@ -76,11 +77,13 @@ const _filterContentTextByLang = ({text, html, markdown, overrideLanguage, defau return langAndContentItems; } -const ContentSpan = ({lang, content, isHTML}) => { +const ContentSpan = ({lang, content, isHTML, markdown}) => { return isHTML ? <span className={`contentSpan ${lang}`} lang={lang} key={lang} dangerouslySetInnerHTML={{__html: content}}/> - : - <span className={`contentSpan ${lang}`} lang={lang} key={lang}>{content}</span>; + : markdown ? <span className={`contentSpan ${lang}`} lang={lang} key={lang}> + <ReactMarkdown className={'reactMarkdown'} unwrapDisallowed={true} disallowedElements={['p']}>{content}</ReactMarkdown> + </span> + : <span className={`contentSpan ${lang}`} lang={lang} key={lang}>{content}</span>; } export {ContentText, VersionContent}; From 63f665fee2598f7b3c1bd989dc7d8d1a1416a5a3 Mon Sep 17 00:00:00 2001 From: yonadavGit <yonadav.le@gmail.com> Date: Tue, 2 Jan 2024 16:32:13 +0200 Subject: [PATCH 243/260] chore(AboutNavSideBar): make 'play button' vector face right --- static/icons/vector-turning-left.svg | 5 ----- static/js/NavSidebar.jsx | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 static/icons/vector-turning-left.svg diff --git a/static/icons/vector-turning-left.svg b/static/icons/vector-turning-left.svg deleted file mode 100644 index 136c4325a1..0000000000 --- a/static/icons/vector-turning-left.svg +++ /dev/null @@ -1,5 +0,0 @@ -<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"> - <g transform="rotate(-180 9 9.5)"> - <path d="M9 2C13.1355 2 16.5 5.3645 16.5 9.5C16.5 13.6355 13.1355 17 9 17C4.8645 17 1.5 13.6355 1.5 9.5C1.5 5.3645 4.8645 2 9 2ZM9 0.5C4.02975 0.5 0 4.52975 0 9.5C0 14.4703 4.02975 18.5 9 18.5C13.9703 18.5 18 14.4703 18 9.5C18 4.52975 13.9703 0.5 9 0.5ZM6.75 13.25V5.75L13.5 9.6095L6.75 13.25Z" fill="white"/> - </g> -</svg> diff --git a/static/js/NavSidebar.jsx b/static/js/NavSidebar.jsx index dfbaedae0e..139b11254a 100644 --- a/static/js/NavSidebar.jsx +++ b/static/js/NavSidebar.jsx @@ -123,7 +123,7 @@ const AboutSefaria = ({hideTitle}) => ( </EnglishText> <HebrewText> <a className="button get-start" href="https://youtu.be/rCADxtqPqnw"> - <img src="/static/icons/vector-turning-left.svg"/> + <img src="/static/icons/vector.svg"/> <div className="get-start"> הכירו את ספריא (2 דק') </div> From cd53180d553884cb94addafefa86c18e790df896 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler@sefaria.org> Date: Tue, 2 Jan 2024 23:45:56 -0500 Subject: [PATCH 244/260] chore(urls): Add a new redirect for tracking donations from @EliezerIsrael's Munich promotional presentation --- sites/sefaria/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sites/sefaria/urls.py b/sites/sefaria/urls.py index 45c2ed2a86..8ba076b345 100644 --- a/sites/sefaria/urls.py +++ b/sites/sefaria/urls.py @@ -85,6 +85,7 @@ url(r'^faq/?$', lambda x: HttpResponseRedirect('/collections/sefaria-faqs' if x.interfaceLang == 'english' else '/collections/%D7%A9%D7%90%D7%9C%D7%95%D7%AA-%D7%A0%D7%A4%D7%95%D7%A6%D7%95%D7%AA-%D7%91%D7%A1%D7%A4%D7%A8%D7%99%D7%90')), url(r'^help/?$', lambda x: HttpResponseRedirect('/collections/sefaria-faqs' if x.interfaceLang == 'english' else '/collections/%D7%A9%D7%90%D7%9C%D7%95%D7%AA-%D7%A0%D7%A4%D7%95%D7%A6%D7%95%D7%AA-%D7%91%D7%A1%D7%A4%D7%A8%D7%99%D7%90')), url(r'^gala/?$', lambda x: HttpResponseRedirect('https://donate.sefaria.org/event/sefarias-10-year-anniversary-gala/e486954')), + url(r'^give/?$', lambda x: HttpResponseRedirect('https://donate.sefaria.org/give/550774/#!/donation/checkout?c_src=mu')), url(r'^jfn?$', lambda x: HttpResponseRedirect('https://www.sefaria.org/sheets/60494')), url(r'^[nN]echama/?', lambda x: HttpResponseRedirect("/collections/גיליונות-נחמה")), url(r'^contest?', lambda x: HttpResponseRedirect("/powered-by-sefaria-contest-2020")), From 91ca4d4856680abb90f7a0b85759af0ffcbd1e57 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler.cohen@gmail.com> Date: Wed, 3 Jan 2024 18:04:13 -0500 Subject: [PATCH 245/260] chore(urls): Add generic method of redirecting to donation campaign pages based on campaign ID. Also provide a default in case no ID was given --- sites/sefaria/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sites/sefaria/urls.py b/sites/sefaria/urls.py index 8ba076b345..4e4dcc19c9 100644 --- a/sites/sefaria/urls.py +++ b/sites/sefaria/urls.py @@ -85,7 +85,7 @@ url(r'^faq/?$', lambda x: HttpResponseRedirect('/collections/sefaria-faqs' if x.interfaceLang == 'english' else '/collections/%D7%A9%D7%90%D7%9C%D7%95%D7%AA-%D7%A0%D7%A4%D7%95%D7%A6%D7%95%D7%AA-%D7%91%D7%A1%D7%A4%D7%A8%D7%99%D7%90')), url(r'^help/?$', lambda x: HttpResponseRedirect('/collections/sefaria-faqs' if x.interfaceLang == 'english' else '/collections/%D7%A9%D7%90%D7%9C%D7%95%D7%AA-%D7%A0%D7%A4%D7%95%D7%A6%D7%95%D7%AA-%D7%91%D7%A1%D7%A4%D7%A8%D7%99%D7%90')), url(r'^gala/?$', lambda x: HttpResponseRedirect('https://donate.sefaria.org/event/sefarias-10-year-anniversary-gala/e486954')), - url(r'^give/?$', lambda x: HttpResponseRedirect('https://donate.sefaria.org/give/550774/#!/donation/checkout?c_src=mu')), + url(r'^give/(?P<campaign_id>[a-zA-Z0-9]+)/?$', lambda x, campaign_id: HttpResponseRedirect(f'https://donate.sefaria.org/give/550774/#!/donation/checkout?c_src={campaign_id}')), url(r'^jfn?$', lambda x: HttpResponseRedirect('https://www.sefaria.org/sheets/60494')), url(r'^[nN]echama/?', lambda x: HttpResponseRedirect("/collections/גיליונות-נחמה")), url(r'^contest?', lambda x: HttpResponseRedirect("/powered-by-sefaria-contest-2020")), From 5def4e3ed7c87aecd82b9612922a007413eedb42 Mon Sep 17 00:00:00 2001 From: Skyler Cohen <skyler.cohen@gmail.com> Date: Wed, 3 Jan 2024 18:07:31 -0500 Subject: [PATCH 246/260] chore(urls): Provide a default in case no ID was given --- sites/sefaria/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sites/sefaria/urls.py b/sites/sefaria/urls.py index 4e4dcc19c9..164ca37fde 100644 --- a/sites/sefaria/urls.py +++ b/sites/sefaria/urls.py @@ -85,7 +85,8 @@ url(r'^faq/?$', lambda x: HttpResponseRedirect('/collections/sefaria-faqs' if x.interfaceLang == 'english' else '/collections/%D7%A9%D7%90%D7%9C%D7%95%D7%AA-%D7%A0%D7%A4%D7%95%D7%A6%D7%95%D7%AA-%D7%91%D7%A1%D7%A4%D7%A8%D7%99%D7%90')), url(r'^help/?$', lambda x: HttpResponseRedirect('/collections/sefaria-faqs' if x.interfaceLang == 'english' else '/collections/%D7%A9%D7%90%D7%9C%D7%95%D7%AA-%D7%A0%D7%A4%D7%95%D7%A6%D7%95%D7%AA-%D7%91%D7%A1%D7%A4%D7%A8%D7%99%D7%90')), url(r'^gala/?$', lambda x: HttpResponseRedirect('https://donate.sefaria.org/event/sefarias-10-year-anniversary-gala/e486954')), - url(r'^give/(?P<campaign_id>[a-zA-Z0-9]+)/?$', lambda x, campaign_id: HttpResponseRedirect(f'https://donate.sefaria.org/give/550774/#!/donation/checkout?c_src={campaign_id}')), + url(r'^give/(?P<campaign_id>[a-zA-Z0-9]+)/?$', lambda x, campaign_id: HttpResponseRedirect(f'https://donate.sefaria.org/give/550774/#!/donation/checkout?c_src={campaign_id}')), + url(r'^give/?$', lambda x: HttpResponseRedirect(f'https://donate.sefaria.org/give/550774/#!/donation/checkout?c_src=mu')), url(r'^jfn?$', lambda x: HttpResponseRedirect('https://www.sefaria.org/sheets/60494')), url(r'^[nN]echama/?', lambda x: HttpResponseRedirect("/collections/גיליונות-נחמה")), url(r'^contest?', lambda x: HttpResponseRedirect("/powered-by-sefaria-contest-2020")), From 2ccf84d954fa856dfbc436a3c408d06e28d9a4bf Mon Sep 17 00:00:00 2001 From: YishaiGlasner <ishaigla@gmail.com> Date: Mon, 8 Jan 2024 09:20:53 +0200 Subject: [PATCH 247/260] test: change ref tests that used mekhilta (after refoactoring of mekhilta) --- sefaria/model/tests/ref_test.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sefaria/model/tests/ref_test.py b/sefaria/model/tests/ref_test.py index b769f5dd87..e5b5c231bc 100644 --- a/sefaria/model/tests/ref_test.py +++ b/sefaria/model/tests/ref_test.py @@ -177,9 +177,8 @@ def test_next_ref(self): assert Ref("Shabbat 4b").next_section_ref().normal() == "Shabbat 5a" assert Ref("Shabbat 5a").next_section_ref().normal() == "Shabbat 5b" assert Ref("Rashi on Genesis 5:32:2").next_section_ref().normal() == "Rashi on Genesis 6:2" - assert Ref("Mekhilta_DeRabbi_Yishmael.35.3").next_section_ref() is None - # This will start to fail when we fill in this text - assert Ref("Mekhilta_DeRabbi_Yishmael.23.19").next_section_ref().normal() == "Mekhilta DeRabbi Yishmael 31:12" + assert Ref("Berakhot 64a").next_section_ref() is None + assert Ref("Rif Chullin 43a").next_section_ref().normal() == "Rif Chullin 44b" def test_complex_next_ref(self): #at time of test we only had complex commentaries stable to test with assert Ref('Pesach Haggadah, Kadesh').next_section_ref().normal() == 'Pesach Haggadah, Urchatz' @@ -197,9 +196,8 @@ def test_prev_ref(self): assert Ref("Shabbat 4b").prev_section_ref().normal() == "Shabbat 4a" assert Ref("Shabbat 5a").prev_section_ref().normal() == "Shabbat 4b" assert Ref("Rashi on Genesis 6:2:1").prev_section_ref().normal() == "Rashi on Genesis 5:32" - assert Ref("Mekhilta 12:1").prev_section_ref() is None - # This will start to fail when we fill in this text - assert Ref("Mekhilta_DeRabbi_Yishmael.31.12").prev_section_ref().normal() == "Mekhilta DeRabbi Yishmael 23:19" + assert Ref("Berakhot 2a").prev_section_ref() is None + assert Ref("Rif Chullin 44b").prev_section_ref().normal() == "Rif Chullin 43a" def test_complex_prev_ref(self): assert Ref('Pesach Haggadah, Urchatz').prev_section_ref().normal() == 'Pesach Haggadah, Kadesh' From 7686dd2134f7fe3668fe52746a1d46cfbfb0ffd4 Mon Sep 17 00:00:00 2001 From: Brendan Galloway <brendan@flanksource.com> Date: Tue, 9 Jan 2024 09:57:23 +0200 Subject: [PATCH 248/260] helm(feat): allocate more resources to mongo restore pod --- helm-chart/sefaria-project/templates/jobs/mongo-restore.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/helm-chart/sefaria-project/templates/jobs/mongo-restore.yaml b/helm-chart/sefaria-project/templates/jobs/mongo-restore.yaml index edd5d85b86..0969c121d0 100644 --- a/helm-chart/sefaria-project/templates/jobs/mongo-restore.yaml +++ b/helm-chart/sefaria-project/templates/jobs/mongo-restore.yaml @@ -40,6 +40,11 @@ spec: containers: - name: mongo-backup-restorer-{{ .Values.deployEnv }} image: mongo:4.4 + resources: + requests: + cpu: 500m + limits: + cpu: 750m volumeMounts: - name: shared-volume mountPath: /storage From 7844d8984eba5af09789013fb8303a8a5ec1ac19 Mon Sep 17 00:00:00 2001 From: Brendan Galloway <brendan@flanksource.com> Date: Tue, 9 Jan 2024 12:57:16 +0200 Subject: [PATCH 249/260] ci: update monorepo release rules --- helm-chart/release-rules.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/helm-chart/release-rules.sh b/helm-chart/release-rules.sh index 8e8cf05ac0..649e51b604 100755 --- a/helm-chart/release-rules.sh +++ b/helm-chart/release-rules.sh @@ -8,15 +8,15 @@ plugins: releaseRules: - {"type": "helm", "release": "minor" } - {"type": "helm", "scope": "fix", "release": "patch" } - - {"type": "feat", "release": false} - - {"type": "fix", "release": false} - - {"type": "chore", "release": false} - - {"type": "docs", "release": false} - - {"type": "style", "release": false} - - {"type": "refactor", "release": false} - - {"type": "perf", "release": false} - - {"type": "test", "release": false} - - {"type": "static", "release": false} + - {"type": "feat", "release": "minor"} + - {"type": "fix", "release": "patch"} + - {"type": "chore", "release": "patch"} + - {"type": "docs", "release": "patch"} + - {"type": "style", "release": "patch"} + - {"type": "refactor", "release": "patch"} + - {"type": "perf", "release": "patch"} + - {"type": "test", "release": "patch"} + - {"type": "static", "release": "patch"} parserOpts: noteKeywords: - MAJOR RELEASE From 5217da9b5e22bb6e529f9f5bfa01e1b76b3bf0bb Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Wed, 10 Jan 2024 09:54:14 +0200 Subject: [PATCH 250/260] fix: fully convert tref to URL form before looking it up in the legacy ref mapping --- sefaria/helper/legacy_ref.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sefaria/helper/legacy_ref.py b/sefaria/helper/legacy_ref.py index 92a303ad14..2c384a15ac 100644 --- a/sefaria/helper/legacy_ref.py +++ b/sefaria/helper/legacy_ref.py @@ -33,6 +33,7 @@ class LegacyRefParsingData(AbstractMongoRecord): } ``` To be used with LegacyRefParser classes in this module. + Current assumption is all trefs in the mapping (both old and mapped) are in URL form and are segment level. """ collection = 'legacy_ref_data' criteria_field = 'title' @@ -109,8 +110,9 @@ def __range_list(ranged_tref: str) -> List[str]: return range_list def __get_mapped_tref(self, legacy_tref: str) -> str: - # replace last space before sections with a period to conform with normal form + # replace last space before sections with a period to conform with url form legacy_tref = re.sub(r" (?=[\d.:ab]+$)", ".", legacy_tref) + legacy_tref = legacy_tref.replace(' ', '_') try: return self._mapping[legacy_tref] except KeyError as err: From 85d208ff623448a46fae9d64f067b25f7861e9b0 Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Wed, 10 Jan 2024 10:15:31 +0200 Subject: [PATCH 251/260] test(legacy ref): modify legacy ref tests to use a title with a space in it that is more general. --- sefaria/helper/tests/legacy_ref_test.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/sefaria/helper/tests/legacy_ref_test.py b/sefaria/helper/tests/legacy_ref_test.py index 934b205666..4b41b1baaa 100644 --- a/sefaria/helper/tests/legacy_ref_test.py +++ b/sefaria/helper/tests/legacy_ref_test.py @@ -46,14 +46,14 @@ def test_zohar_index(test_index_title): @pytest.fixture(scope="module", autouse=True) -def test_zohar_mapping_data(test_index_title): +def test_zohar_mapping_data(test_index_title, url_test_index_title): lrpd = LegacyRefParsingData({ "index_title": test_index_title, "data": { "mapping": { - f"{test_index_title}.1.15a.1": f"{test_index_title}.1.42", - f"{test_index_title}.1.15a.2": f"{test_index_title}.1.42", - f"{test_index_title}.1.15a.3": f"{test_index_title}.1.43", + f"{url_test_index_title}.1.15a.1": f"{url_test_index_title}.1.42", + f"{url_test_index_title}.1.15a.2": f"{url_test_index_title}.1.42", + f"{url_test_index_title}.1.15a.3": f"{url_test_index_title}.1.43", }, }, }) @@ -66,15 +66,20 @@ def test_zohar_mapping_data(test_index_title): @pytest.fixture(scope="module") def test_index_title(): - return "TestZohar" + return "Test Zohar" @pytest.fixture(scope="module") -def old_and_new_trefs(request, test_index_title): +def url_test_index_title(test_index_title): + return test_index_title.replace(" ", "_") + + +@pytest.fixture(scope="module") +def old_and_new_trefs(request, url_test_index_title): old_ref, new_ref = request.param # if new_ref is None, means mapping doesn't exist - new_ref = new_ref and f"{test_index_title}.{new_ref}" - return f"{test_index_title}.{old_ref}", new_ref + new_ref = new_ref and f"{url_test_index_title}.{new_ref}" + return f"{url_test_index_title}.{old_ref}", new_ref def get_book(tref): @@ -143,12 +148,12 @@ def test_old_zohar_partial_ref_legacy_parsing(self, old_and_new_trefs): assert converted_ref.legacy_tref == old_ref assert converted_ref.normal() == Ref(new_ref).normal() - def test_instantiate_ref_with_legacy_parse_fallback(self, test_index_title, old_and_new_trefs): + def test_instantiate_ref_with_legacy_parse_fallback(self, url_test_index_title, old_and_new_trefs): old_tref, new_tref = old_and_new_trefs oref = Ref.instantiate_ref_with_legacy_parse_fallback(old_tref) if new_tref is None: - assert oref.url() == test_index_title + assert oref.url() == url_test_index_title assert getattr(oref, 'legacy_tref', None) is None else: assert oref.url() == new_tref From b8caab3dc3874102b532472ae8a9fbab6f5d54c4 Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Wed, 10 Jan 2024 10:15:59 +0200 Subject: [PATCH 252/260] fix(legacy ref): make sure legacy tref is in URL form --- sefaria/helper/legacy_ref.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sefaria/helper/legacy_ref.py b/sefaria/helper/legacy_ref.py index 2c384a15ac..288006446e 100644 --- a/sefaria/helper/legacy_ref.py +++ b/sefaria/helper/legacy_ref.py @@ -121,14 +121,14 @@ def __get_mapped_tref(self, legacy_tref: str) -> str: def __parse_segment_ref(self, legacy_tref: str) -> Ref: converted_tref = self.__get_mapped_tref(legacy_tref) converted_ref = Ref(converted_tref) - converted_ref.legacy_tref = legacy_tref + converted_ref.legacy_tref = legacy_tref.replace(" ", "_") return converted_ref def __parse_ranged_ref(self, legacy_tref: str) -> Ref: parsed_range_list = [self.__parse_segment_ref(temp_tref) for temp_tref in self.__range_list(legacy_tref)] parsed_range_list.sort(key=lambda x: x.order_id()) # not assuming mapping is in order ranged_oref = parsed_range_list[0].to(parsed_range_list[-1]) - ranged_oref.legacy_tref = legacy_tref + ranged_oref.legacy_tref = legacy_tref.replace(" ", "_") return ranged_oref From 96be1df10207b44286cc31663da88366a4a133a8 Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Wed, 10 Jan 2024 10:51:52 +0200 Subject: [PATCH 253/260] refactor(legacy ref): move url form logic to function --- sefaria/helper/legacy_ref.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/sefaria/helper/legacy_ref.py b/sefaria/helper/legacy_ref.py index 288006446e..1042004e0c 100644 --- a/sefaria/helper/legacy_ref.py +++ b/sefaria/helper/legacy_ref.py @@ -86,10 +86,22 @@ def parse(self, legacy_tref: str) -> Ref: @param legacy_tref: @return: """ + legacy_tref = self.__to_url_form(legacy_tref) if self.__is_ranged_ref(legacy_tref): return self.__parse_ranged_ref(legacy_tref) return self.__parse_segment_ref(legacy_tref) + @staticmethod + def __to_url_form(tref: str): + """ + replace last space before sections with a period + AND + then replace remaining spaces with underscore + @param tref: + @return: + """ + return re.sub(r" (?=[\d.:ab]+$)", ".", tref).replace(" ", "_") + @staticmethod def __is_ranged_ref(tref: str) -> bool: return "-" in tref @@ -110,9 +122,6 @@ def __range_list(ranged_tref: str) -> List[str]: return range_list def __get_mapped_tref(self, legacy_tref: str) -> str: - # replace last space before sections with a period to conform with url form - legacy_tref = re.sub(r" (?=[\d.:ab]+$)", ".", legacy_tref) - legacy_tref = legacy_tref.replace(' ', '_') try: return self._mapping[legacy_tref] except KeyError as err: @@ -121,14 +130,14 @@ def __get_mapped_tref(self, legacy_tref: str) -> str: def __parse_segment_ref(self, legacy_tref: str) -> Ref: converted_tref = self.__get_mapped_tref(legacy_tref) converted_ref = Ref(converted_tref) - converted_ref.legacy_tref = legacy_tref.replace(" ", "_") + converted_ref.legacy_tref = legacy_tref return converted_ref def __parse_ranged_ref(self, legacy_tref: str) -> Ref: parsed_range_list = [self.__parse_segment_ref(temp_tref) for temp_tref in self.__range_list(legacy_tref)] parsed_range_list.sort(key=lambda x: x.order_id()) # not assuming mapping is in order ranged_oref = parsed_range_list[0].to(parsed_range_list[-1]) - ranged_oref.legacy_tref = legacy_tref.replace(" ", "_") + ranged_oref.legacy_tref = legacy_tref return ranged_oref From 6d2f96710878b8bf5f1d8a365c729e76e0bde6a3 Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Wed, 10 Jan 2024 12:39:46 +0200 Subject: [PATCH 254/260] test(search): fix failing search filter test --- sefaria/helper/tests/search_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sefaria/helper/tests/search_test.py b/sefaria/helper/tests/search_test.py index 08ce2dac08..fdc62ed06c 100644 --- a/sefaria/helper/tests/search_test.py +++ b/sefaria/helper/tests/search_test.py @@ -22,7 +22,7 @@ def test_query_obj(): assert ordered(t) == ordered(s.to_dict()) # text query sorted by pagerank and with multiple applied filters s = get_query_obj("moshe", "text", "naive_lemmatizer", False, 10, 0, 10, ["Tanakh/Targum/Targum Jonathan", "Mishnah/Seder Zeraim/Mishnah Peah", "Talmud/Bavli/Seder Moed/Pesachim"], ["path", "path", "path"], [], "score", ['pagesheetrank'], sort_score_missing=0.04) - t = json.loads("""{"_source": false, "from":0,"size":10,"highlight":{"fields":{"naive_lemmatizer":{"fragment_size":200,"pre_tags":["<b>"],"post_tags":["</b>"]}}},"query":{"function_score":{"functions":[{"field_value_factor":{"field":"pagesheetrank","missing":0.04}}],"query":{"bool":{"must":[{"match_phrase":{"naive_lemmatizer":{"query":"moshe","slop":10}}}],"filter":[{"bool":{"must":[{"bool":{"should":[{"regexp":{"path":"Tanakh/Targum/Targum\\\\ Jonathan.*"}},{"regexp":{"path":"Mishnah/Seder\\\\ Zeraim/Mishnah\\\\ Peah.*"}},{"regexp":{"path":"Talmud/Bavli/Seder\\\\ Moed/Pesachim.*"}}]}}]}}]}}}}}""") + t = json.loads("""{"_source": false, "from":0,"size":10,"highlight":{"fields":{"naive_lemmatizer":{"fragment_size":200,"pre_tags":["<b>"],"post_tags":["</b>"]}}},"query":{"function_score":{"functions":[{"field_value_factor":{"field":"pagesheetrank","missing":0.04}}],"query":{"bool":{"must":[{"match_phrase":{"naive_lemmatizer":{"query":"moshe","slop":10}}}],"filter":[{"bool":{"must":[{"bool":{"should":[{"regexp":{"path":"Tanakh/Targum/Targum\\\\ Jonathan|Tanakh/Targum/Targum\\\\ Jonathan/.*"}},{"regexp":{"path":"Mishnah/Seder\\\\ Zeraim/Mishnah\\\\ Peah|Mishnah/Seder\\\\ Zeraim/Mishnah\\\\ Peah/.*"}},{"regexp":{"path":"Talmud/Bavli/Seder\\\\ Moed/Pesachim|Talmud/Bavli/Seder\\\\ Moed/Pesachim/.*"}}]}}]}}]}}}}}""") assert ordered(t) == ordered(s.to_dict()) # sheet query sorted by relevance, with a collections agg and collections/tag filters s = get_query_obj("moshe", "sheet", "content", False, 10, 0, 10, ["", "Moses", "Passover"], ["collections", "tags", "tags"], ['collections'], "score", []) From e0e66b1885624f2ba6c89d60cc49b7f94d5f6f75 Mon Sep 17 00:00:00 2001 From: Brendan Galloway <brendan@flanksource.com> Date: Wed, 10 Jan 2024 13:03:28 +0200 Subject: [PATCH 255/260] ci: restrict pytest concurrency to test possible resource constraints --- .github/workflows/continuous.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 4c9dfd788f..587dcd0963 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -196,6 +196,9 @@ jobs: run: cat /home/runner/jestResults.json; STATUS=`jq ".numFailedTestSuites" /home/runner/jestResults.json`; exit $STATUS if: ${{ always() }} sandbox-deploy: + concurrency: + group: dev-mongo + cancel-in-progress: false if: ${{ github.event_name == 'pull_request' }} runs-on: ubuntu-latest needs: build-derived @@ -265,6 +268,9 @@ jobs: WAIT_DURATION: "3000" GIT_COMMIT: "${{ steps.get-sha.outputs.sha_short }}" pytest-job: + concurrency: + group: dev-mongo + cancel-in-progress: false if: ${{ github.event_name == 'pull_request' }} name: "PyTest" # This name is referenced when slacking status needs: From 6d3bfd71a2702d32b9212788962626bfda393b56 Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Wed, 10 Jan 2024 14:52:15 +0200 Subject: [PATCH 256/260] fix(legacy): replace input title with normalized title in case input was an alt title --- sefaria/model/text.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 22c2e19199..0e0aadfd20 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -4873,6 +4873,8 @@ def instantiate_ref_with_legacy_parse_fallback(tref: str) -> 'Ref': except PartialRefInputError as e: matched_ref = Ref(e.matched_part) try: + # replace input title with normalized title in case input was an alt title + tref = tref.replace(e.matched_part, matched_ref.normal()) tref = Ref.__clean_tref(tref, matched_ref._lang) legacy_ref_parser = legacy_ref_parser_handler[matched_ref.index.title] return legacy_ref_parser.parse(tref) From 3fb1b5eb332d6c3d4b1c15fc5615707aa4c2c849 Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Wed, 10 Jan 2024 15:30:58 +0200 Subject: [PATCH 257/260] test(legacy ref): add alt title legacy ref test --- sefaria/helper/tests/legacy_ref_test.py | 45 ++++++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/sefaria/helper/tests/legacy_ref_test.py b/sefaria/helper/tests/legacy_ref_test.py index 4b41b1baaa..9c9e0bd071 100644 --- a/sefaria/helper/tests/legacy_ref_test.py +++ b/sefaria/helper/tests/legacy_ref_test.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import pytest +import re from sefaria.model import * from sefaria.helper.legacy_ref import legacy_ref_parser_handler, MappingLegacyRefParser, NoLegacyRefParserError, LegacyRefParsingData, LegacyRefParserMappingKeyError from sefaria.system.exceptions import PartialRefInputError @@ -21,6 +22,10 @@ def test_zohar_index(test_index_title): "text": en_title, "primary": True }, + { + "lang": "en", + "text": "Alt Title Yo yo" + }, { "lang": "he", "text": 'זהר לא אמיתי', @@ -150,19 +155,33 @@ def test_old_zohar_partial_ref_legacy_parsing(self, old_and_new_trefs): def test_instantiate_ref_with_legacy_parse_fallback(self, url_test_index_title, old_and_new_trefs): old_tref, new_tref = old_and_new_trefs - - oref = Ref.instantiate_ref_with_legacy_parse_fallback(old_tref) - if new_tref is None: - assert oref.url() == url_test_index_title - assert getattr(oref, 'legacy_tref', None) is None - else: - assert oref.url() == new_tref - assert oref.legacy_tref == old_tref.replace(':', '.') - - if new_tref is not None: - oref = Ref.instantiate_ref_with_legacy_parse_fallback(new_tref) - assert oref.url() == new_tref - assert getattr(oref, 'legacy_tref', None) is None + instantiate_legacy_refs_tester(url_test_index_title, old_tref, new_tref) + + +@pytest.mark.parametrize(('url_index_title', 'input_title', 'input_sections', 'output_tref'), [ + ["Test_Zohar", "Alt Title Yo yo", "1:15a:1", "Test_Zohar.1.42"], +]) +def test_instantiate_legacy_refs_parametrized(url_index_title, input_title, input_sections, output_tref): + old_tref = f"{input_title}.{input_sections}" + instantiate_legacy_refs_tester(url_index_title, old_tref, output_tref, old_title=input_title) + + +def instantiate_legacy_refs_tester(url_index_title, old_tref, new_tref, old_title=None): + oref = Ref.instantiate_ref_with_legacy_parse_fallback(old_tref) + if new_tref is None: + assert oref.url() == url_index_title + assert getattr(oref, 'legacy_tref', None) is None + else: + assert oref.url() == new_tref + expected_legacy_tref = old_tref.replace(':', '.') + if old_title: + expected_legacy_tref = expected_legacy_tref.replace(old_title, url_index_title) + assert oref.legacy_tref == expected_legacy_tref + + if new_tref is not None: + oref = Ref.instantiate_ref_with_legacy_parse_fallback(new_tref) + assert oref.url() == new_tref + assert getattr(oref, 'legacy_tref', None) is None class TestLegacyRefsRandomIndex: From a8aeaeeb006806b7786f48ab4aa9879a5535794e Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Wed, 10 Jan 2024 21:54:55 +0200 Subject: [PATCH 258/260] fix(legacy ref): clean tref of underscores before replacing alt title so alt title will match exactly --- sefaria/helper/tests/legacy_ref_test.py | 1 + sefaria/model/text.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sefaria/helper/tests/legacy_ref_test.py b/sefaria/helper/tests/legacy_ref_test.py index 9c9e0bd071..8880d14500 100644 --- a/sefaria/helper/tests/legacy_ref_test.py +++ b/sefaria/helper/tests/legacy_ref_test.py @@ -160,6 +160,7 @@ def test_instantiate_ref_with_legacy_parse_fallback(self, url_test_index_title, @pytest.mark.parametrize(('url_index_title', 'input_title', 'input_sections', 'output_tref'), [ ["Test_Zohar", "Alt Title Yo yo", "1:15a:1", "Test_Zohar.1.42"], + ["Test_Zohar", "Alt_Title_Yo_yo", "1:15a:1", "Test_Zohar.1.42"], ]) def test_instantiate_legacy_refs_parametrized(url_index_title, input_title, input_sections, output_tref): old_tref = f"{input_title}.{input_sections}" diff --git a/sefaria/model/text.py b/sefaria/model/text.py index 0e0aadfd20..6e614c61b3 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -4873,9 +4873,9 @@ def instantiate_ref_with_legacy_parse_fallback(tref: str) -> 'Ref': except PartialRefInputError as e: matched_ref = Ref(e.matched_part) try: + tref = Ref.__clean_tref(tref, matched_ref._lang) # replace input title with normalized title in case input was an alt title tref = tref.replace(e.matched_part, matched_ref.normal()) - tref = Ref.__clean_tref(tref, matched_ref._lang) legacy_ref_parser = legacy_ref_parser_handler[matched_ref.index.title] return legacy_ref_parser.parse(tref) except LegacyRefParserError: From 38704d33665292719493a3c1ba8c862baa459071 Mon Sep 17 00:00:00 2001 From: stevekaplan123 <stevek004@gmail.com> Date: Mon, 15 Jan 2024 09:49:31 +0200 Subject: [PATCH 259/260] fix(Search Result): markdown should be properly rendered --- static/js/SearchResultList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/SearchResultList.jsx b/static/js/SearchResultList.jsx index b09e91b170..6fc8173789 100644 --- a/static/js/SearchResultList.jsx +++ b/static/js/SearchResultList.jsx @@ -69,7 +69,7 @@ const SearchTopic = (props) => { {topicCategory} {"enDesc" in props.topic ? <div className="topicDescSearchResult systemText"> - <InterfaceText text={{en:props.topic.enDesc, he:props.topic.heDesc}}/> + <InterfaceText markdown={{en:props.topic.enDesc, he:props.topic.heDesc}}/> </div> : null} {sourcesSheetsDiv} </div> From 2181ef338af0401978374a40d0a86fb607bf77d4 Mon Sep 17 00:00:00 2001 From: nsantacruz <noahssantacruz@gmail.com> Date: Tue, 16 Jan 2024 11:04:47 +0200 Subject: [PATCH 260/260] fix(search): remove newline that was auto-added --- .../templates/cronjob/reindex-elasticsearch.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml index 7aab052a47..06bae04bdb 100644 --- a/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml +++ b/helm-chart/sefaria-project/templates/cronjob/reindex-elasticsearch.yaml @@ -64,8 +64,7 @@ spec: command: ["bash"] args: [ "-c", - "mkdir -p /log && touch /log/sefaria_book_errors.log && pip install numpy elasticsearch==8.8.2 git+https://github.com/Sefaria/elasticsearch-dsl-py@v8.0 - .0#egg=elasticsearch-dsl && /app/run /app/scripts/scheduled/reindex_elasticsearch_cronjob.py" + "mkdir -p /log && touch /log/sefaria_book_errors.log && pip install numpy elasticsearch==8.8.2 git+https://github.com/Sefaria/elasticsearch-dsl-py@v8.0.0#egg=elasticsearch-dsl && /app/run /app/scripts/scheduled/reindex_elasticsearch_cronjob.py" ] restartPolicy: Never volumes: