diff --git a/locale/he/LC_MESSAGES/django.mo b/locale/he/LC_MESSAGES/django.mo index 9cc379dd37..edd24305ea 100644 Binary files a/locale/he/LC_MESSAGES/django.mo and b/locale/he/LC_MESSAGES/django.mo differ diff --git a/locale/he/LC_MESSAGES/django.po b/locale/he/LC_MESSAGES/django.po index 2a93d73bfe..d2d98b9575 100644 --- a/locale/he/LC_MESSAGES/django.po +++ b/locale/he/LC_MESSAGES/django.po @@ -798,7 +798,7 @@ msgstr "དཔེ་ཆ་" #: templates/registration/password_reset_complete.html:10 msgid "registration.password_reset_complete.your_new_password_has_been_saved" -msgstr "" +msgstr "ཁྱེད་ཀྱི་གསང་ཚིག་གསར་པ་ཉར་ཚགས་བྱས་ཚར།" #: templates/registration/password_reset_complete.html:13 msgid "registration.password_reset_complete.back_to_login" diff --git a/locale/zh_Hans/LC_MESSAGES/django.mo b/locale/zh_Hans/LC_MESSAGES/django.mo index 7b5926cc92..8ddb3d0529 100644 Binary files a/locale/zh_Hans/LC_MESSAGES/django.mo and b/locale/zh_Hans/LC_MESSAGES/django.mo differ diff --git a/locale/zh_Hans/LC_MESSAGES/django.po b/locale/zh_Hans/LC_MESSAGES/django.po index 85148e5ea2..3794e0c635 100644 --- a/locale/zh_Hans/LC_MESSAGES/django.po +++ b/locale/zh_Hans/LC_MESSAGES/django.po @@ -20,15 +20,15 @@ msgstr "" #: sefaria/forms.py:32 msgid "placeholder_email_delete_message" -msgstr "" +msgstr "电子邮件地址删除" #: sefaria/forms.py:33 sefaria/forms.py:37 msgid "placeholder_admin_password" -msgstr "" +msgstr "管理员密码" #: sefaria/forms.py:36 msgid "placeholder_sheet_id_delete_message" -msgstr "" +msgstr "删除表ID" #: sefaria/forms.py:41 sefaria/forms.py:47 sefaria/forms.py:152 msgid "placeholder_email_address" @@ -78,11 +78,11 @@ msgstr "普通用户" #: sefaria/forms.py:95 sefaria/model/user_profile.py:283 msgid "user_already_exists_message" -msgstr "" +msgstr "带有该电子邮件的用户已经存在。" #: sefaria/forms.py:143 msgid "mobile_app_key_error_message" -msgstr "" +msgstr "提供了不正确的Mobile_app_key" #: sefaria/forms.py:157 msgid "form.placeholder_new_password" @@ -130,11 +130,11 @@ msgstr "您必须登录才能创建新的收藏" #: sourcesheets/views.py:566 msgid "error_message_when_trying_to_remove_the_last_owner_of_a_collection" -msgstr "" +msgstr "离开这个收藏将使没有任何所有者。请在离开前任命另一个所有者,或删除收藏品。" #: sourcesheets/views.py:1161 msgid "error_message_when_trying_to_upload_media_without_logging_in" -msgstr "" +msgstr "您必须登录才能访问此API。" #: templates/404.html:4 msgid "page_not_found_title" @@ -154,7 +154,9 @@ msgstr "内部服务器错误" #: templates/500.html:15 msgid "send_message_to_developer_request" -msgstr "抱歉,系统出错。我们已通知开发人员。如有疑问或建议,请联系我们的 注意:原文最后一句未完成,所以翻译也相应保留了未完成的状态。这个简短版本保留了原文的主要信息,包括出错通知、开发人员已被告知,以及如何联系的指示" +msgstr "" +"抱歉,系统出错。我们已通知开发人员。如有疑问或建议,请联系我们的 " +"注意:原文最后一句未完成,所以翻译也相应保留了未完成的状态。这个简短版本保留了原文的主要信息,包括出错通知、开发人员已被告知,以及如何联系的指示" #: templates/500.html:15 msgid "developer_team" @@ -194,107 +196,107 @@ msgstr "网站语言" #: templates/account_settings.html:46 msgid "account_setting.language_english_option" -msgstr "" +msgstr "英语" #: templates/account_settings.html:49 msgid "common.account_setting.language_tibetan_option" -msgstr "" +msgstr "藏" #: templates/account_settings.html:56 msgid "account_setting.user's_preferred_translation_language" -msgstr "" +msgstr "首选翻译语言" #: templates/account_settings.html:69 msgid "account_setting.user's_reading_history" -msgstr "" +msgstr "阅读历史" #: templates/account_settings.html:73 msgid "account_setting.reading_history_enable" -msgstr "" +msgstr "在" #: templates/account_settings.html:76 msgid "account_setting.reading_history_disable" -msgstr "" +msgstr "离开" #: templates/account_settings.html:84 msgid "account_setting.login_account_email_message" -msgstr "" +msgstr "帐户电子邮件(用于登录)" #: templates/account_settings.html:89 msgid "account_setting.change_email_message" -msgstr "" +msgstr "更改电子邮件" #: templates/account_settings.html:94 msgid "account_setting.new_email" -msgstr "" +msgstr "新电子邮件" #: templates/account_settings.html:95 msgid "account_setting.confirm_new_email" -msgstr "" +msgstr "确认新电子邮件" #: templates/account_settings.html:96 msgid "common.account_setting.password" -msgstr "" +msgstr "密码" #: templates/account_settings.html:98 msgid "account_setting.update_email" -msgstr "" +msgstr "更新电子邮件" #: templates/account_settings.html:105 msgid "account_setting.google_account_message" -msgstr "" +msgstr "Google帐户(允许将床单导出到Google Drive)" #: templates/account_settings.html:110 msgid "account_setting.google_account_disconnect_with_pecha_acc" -msgstr "" +msgstr "断开" #: templates/account_settings.html:117 msgid "account_setting.google_account_disconnected_message" -msgstr "" +msgstr "您的Google帐户已断开连接" #: templates/account_settings.html:120 msgid "account_setting.export_sheet_message" -msgstr "" +msgstr "您可以在下次导出表格时链接到新的Google帐户" #: templates/account_settings.html:125 msgid "common.account_setting.save_setting_button" -msgstr "" +msgstr "节省" #: templates/account_settings.html:128 msgid "common.account_setting.cancel_request" -msgstr "" +msgstr "取消" #: templates/activity.html:7 templates/activity.html:36 msgid "activity.revision_history" -msgstr "" +msgstr "修订历史" #: templates/activity.html:9 msgid "activity.pecha_activity_profile" -msgstr "" +msgstr "PECHA活动" #: templates/activity.html:11 msgid "activity.pecha_activity" -msgstr "" +msgstr "PECHA活动" #: templates/activity.html:17 msgid "activity.revision_history_message" -msgstr "" +msgstr "在Pecha上查看本文的完整修订历史记录。" #: templates/activity.html:19 msgid "activity.activity_message" -msgstr "" +msgstr "查看用户在PECHA上的活动历史记录,包括已发布的源表以及文本翻译,编辑或添加。" #: templates/activity.html:21 msgid "activity.view_log_message" -msgstr "" +msgstr "查看PECHA上的公共活动的完整日志,包括已发表的源表以及文本翻译,编辑或添加。" #: templates/activity.html:43 msgid "activity.public_activity" -msgstr "" +msgstr "公共活动" #: templates/activity.html:47 msgid "activity.recent_activity" -msgstr "" +msgstr "最近关于Pecha的活动" #: templates/activity.html:52 msgid "activity.all_activity_drop_down" @@ -302,75 +304,75 @@ msgstr "所有活动" #: templates/activity.html:53 msgid "activity.translation_option" -msgstr "" +msgstr "翻译" #: templates/activity.html:54 msgid "activity.source_sheets_option" -msgstr "" +msgstr "源表" #: templates/activity.html:55 msgid "activity.new_texts_option" -msgstr "" +msgstr "新文字" #: templates/activity.html:56 msgid "activity.edit_list_option" -msgstr "" +msgstr "编辑" #: templates/activity.html:57 msgid "activity.notes_option" -msgstr "" +msgstr "笔记" #: templates/activity.html:58 msgid "activity.reviews_option" -msgstr "" +msgstr "评论" #: templates/activity.html:59 msgid "activity.text_info_option_option" -msgstr "" +msgstr "文字信息" #: templates/activity.html:60 msgid "activity.reversions_option" -msgstr "" +msgstr "恢复" #: templates/activity.html:61 msgid "activity.flagged_option" -msgstr "" +msgstr "标记" #: templates/delete-sheet.html:4 msgid "admin.admin_delete_sheet" -msgstr "" +msgstr "管理员:删除表" #: templates/delete-sheet.html:4 msgid "admin.common.delete-sheet.pecha" -msgstr "" +msgstr "关闭" #: templates/delete-sheet.html:11 msgid "admin.delete_sheet" -msgstr "" +msgstr "删除表" #: templates/delete-sheet.html:13 msgid "admin.caution_messsage" -msgstr "" +msgstr "注意这将删除您输入的表ID" #: templates/delete-sheet.html:14 msgid "admin.can't_undone_message" -msgstr "" +msgstr "它不能撤消。" #: templates/delete-sheet.html:19 msgid "admin.delete_sheet_button" -msgstr "" +msgstr "删除表" #: templates/edit_collection.html:9 msgid "edit_collection.title" -msgstr "" +msgstr "在Pecha上编辑收集" #: templates/edit_collection.html:20 msgid "edit_collection.loading" -msgstr "" +msgstr "加载中..." #: templates/edit_profile.html:5 msgid "edit_profile.edit_your_profile" -msgstr "" +msgstr "在Pecha上编辑您的个人资料" #: templates/edit_profile.html:13 msgid "edit_profile.header" @@ -470,140 +472,140 @@ msgstr "油管频道" #: templates/edit_term.html:39 msgid "admin.terms_editor" -msgstr "" +msgstr "术语编辑器" #: templates/edit_term.html:40 msgid "admin.loading" -msgstr "" +msgstr "加载中..." #: templates/edit_text.html:64 msgid "edit_text.add_new_text" -msgstr "" +msgstr "添加新文字" #: templates/edit_text.html:65 msgid "edit_text.text_commentator_name" -msgstr "" +msgstr "文字或评论员名称:" #: templates/edit_text.html:68 msgid "edit_text.add_button" -msgstr "" +msgstr "添加" #: templates/edit_text.html:69 templates/edit_text.html:97 #: templates/edit_text.html:150 msgid "common.edit_text.cancel_button" -msgstr "" +msgstr "取消" #: templates/edit_text.html:86 msgid "edit_text.your_review" -msgstr "" +msgstr "您的评论:" #: templates/edit_text.html:88 msgid "edit_text.review_question" -msgstr "" +msgstr "我应该如何写评论?" #: templates/edit_text.html:96 templates/edit_text.html:149 msgid "common.edit_text.save_button" -msgstr "" +msgstr "节省" #: templates/edit_text.html:101 msgid "edit_text_add_review_request" -msgstr "" +msgstr "要添加您的评论," #: templates/edit_text.html:101 msgid "edit_text_login" -msgstr "" +msgstr "登录" #: templates/edit_text.html:101 msgid "edit_text_or" -msgstr "" +msgstr "或者" #: templates/edit_text.html:101 msgid "edit_text_register" -msgstr "" +msgstr "登记" #: templates/edit_text.html:104 msgid "edit_text.close_button" -msgstr "" +msgstr "关闭" #: templates/edit_text.html:109 msgid "edit_text.review_message" -msgstr "" +msgstr "写有用的评论" #: templates/edit_text.html:110 msgid "edit_text_primary_text_historical_translation" -msgstr "" +msgstr "对于主要文本和历史翻译:" #: templates/edit_text.html:110 msgid "edit_text_primary_text_historical_trans_detail" -msgstr "" +msgstr "指出使用了哪些外部来源(书籍,网页)来比较文本,或者清楚您的评论仅在查看存在的文字。" #: templates/edit_text.html:112 msgid "edit_text.compare_printed_edition" -msgstr "" +msgstr "我将此文本与X印刷版进行了比较,发现没有问题。" #: templates/edit_text.html:113 msgid "edit_text.all_looks_good" -msgstr "" +msgstr "我在X网站上对该版本进行了检查,看上去都不错。" #: templates/edit_text.html:114 msgid "edit_text.no_issue_message" -msgstr "" +msgstr "我仔细阅读了这段文字,看不到任何错误。" #: templates/edit_text.html:119 msgid "edit_text.accurate" -msgstr "" +msgstr "这种翻译对我来说似乎是准确的。" #: templates/edit_text.html:120 msgid "edit_text.translation_review" -msgstr "" +msgstr "该语言可以改进,但翻译似乎是正确的。" #: templates/edit_text.html:121 msgid "edit_text.misunderstand_term_X" -msgstr "" +msgstr "这种翻译误解了X一词。" #: templates/edit_text.html:125 msgid "edit_text.score_review" -msgstr "" +msgstr "如何评分您的评论:" #: templates/edit_text.html:127 msgid "edit_text.compared_to_source_message" -msgstr "" +msgstr "与来源相比,有信心没有问题。" #: templates/edit_text.html:130 msgid "edit_text.compared_to_source_no_problem_message" -msgstr "" +msgstr "与来源相比,没有注意到任何问题。" #: templates/edit_text.html:133 msgid "edit_text.proble_free_need_review" -msgstr "" +msgstr "看起来没有问题,但可以更仔细地进行审查。" #: templates/edit_text.html:136 msgid "edit_text.found_small_issues_need_to_address" -msgstr "" +msgstr "发现一些需要解决的问题。" #: templates/edit_text.html:139 msgid "edit_text.found_serious_issues_need_address" -msgstr "" +msgstr "发现需要解决的严重问题。" #: templates/edit_text.html:142 msgid "edit_text_ok_button" -msgstr "" +msgstr "好的" #: templates/edit_text.html:163 msgid "edit_text.original_translation" -msgstr "" +msgstr "原始翻译" #: templates/edit_text.html:167 msgid "edit_text.copied_text" -msgstr "" +msgstr "复制的文字" #: templates/edit_text.html:178 msgid "edit_text.version_title" -msgstr "" +msgstr "版本标题:" #: templates/edit_text.html:179 msgid "edit_text.copied_from" -msgstr "" +msgstr "复制从:" #: templates/elements/loading.html:12 msgid "common.loading" @@ -611,144 +613,146 @@ msgstr "正在加载..." #: templates/elements/login_prompt.html:6 msgid "common.templates.elements.login_prompts.sign_up" -msgstr "" +msgstr "报名" #: templates/elements/login_prompt.html:8 msgid "common.login_prompt_cancel" -msgstr "" +msgstr "取消" #: templates/email/notification.html:8 msgid "notification.sent_u_msg" -msgstr "" +msgstr "给您发送消息:" #: templates/email/notification.html:13 msgid "notification.reply_to" -msgstr "" +msgstr "回复" #: templates/email/notification.html:17 msgid "notification.source_sheet_like" -msgstr "" +msgstr "喜欢您的源表" #: templates/email/notification.html:20 msgid "notification.published_new_source_sheet" -msgstr "" +msgstr "发布了新的源表" #: templates/email/notification.html:23 msgid "templates.elements.notification.a_user_is_now_following_you" -msgstr "" +msgstr "现在正在关注你。" #: templates/email/notification.html:26 msgid "templates.elements.notification.added_you_to_the_collection" -msgstr "" +msgstr "将您添加到集合中" #: templates/email/notification.html:29 msgid "notification.added_note_to_a_link" -msgstr "" +msgstr "在" #: templates/email/notification.html:31 msgid "notification.discussion_u_follow" -msgstr "" +msgstr "讨论您关注" #: templates/email/notification.html:35 msgid "notification.notice_shouldnt_exit_message" -msgstr "" +msgstr "这是没有类型的通知,不应该存在。" #: templates/people.html:5 msgid "people.authors_on_pecha" -msgstr "" +msgstr "Pecha的作者" #: templates/people.html:7 msgid "people.biography_and_informations_about_authors" -msgstr "" +msgstr "关于Pecha佛教文本的作者的传记和信息,以及指向其可用文本的链接。" #: templates/people.html:23 msgid "people.authors" -msgstr "" +msgstr "作者" #: templates/people.html:28 msgid "people.choejuk_author" -msgstr "" +msgstr "chojuk的人" #: templates/profile.html:4 msgid "profile_title_pecha" -msgstr "" +msgstr "他是监狱" #: templates/profile.html:6 msgid "user_profile_page_desc" -msgstr "" +msgstr "在Pecha上。关注他们的公共资源表,笔记和翻译。" #: templates/profile.html:52 msgid "common.profile.facebook" -msgstr "" +msgstr "Facebook" #: templates/profile.html:58 msgid "common.profile.twitter" -msgstr "" +msgstr "叽叽喳喳" #: templates/profile.html:64 msgid "common.profile.linkedlin" -msgstr "" +msgstr "LinkedIn" #: templates/profile.html:73 msgid "profile.message" -msgstr "" +msgstr "信息" #: templates/profile.html:77 msgid "common.profile.follow" -msgstr "" +msgstr "跟随" #: templates/profile.html:78 msgid "common.profile.following" -msgstr "" +msgstr "下列的" #: templates/profile.html:79 msgid "common.profile.unfollow" -msgstr "" +msgstr "取消关注" #: templates/profile.html:84 msgid "profile.edit_profile" -msgstr "" +msgstr "编辑个人资料" #: templates/profile.html:99 msgid "profile.source_sheet" -msgstr "" +msgstr "源表" #: templates/profile.html:119 msgid "profile_notes" -msgstr "" +msgstr "笔记" #: templates/profile.html:126 msgid "profile.activity" -msgstr "" +msgstr "活动" #: templates/profile.html:128 msgid "profile.more_activity" -msgstr "" +msgstr "更多活动" #: templates/registration/delete_user_account.html:4 msgid "delete_user_account.adming_delete_a_user" -msgstr "" +msgstr "管理员:删除用户" #: templates/registration/delete_user_account.html:4 msgid "common.delete_user_account.pecha" -msgstr "" +msgstr "关闭" #: templates/registration/delete_user_account.html:11 #: templates/registration/delete_user_account.html:17 msgid "registration.delete_user_account.delete_user" -msgstr "" +msgstr "删除用户" #: templates/registration/delete_user_account.html:20 msgid "delete_user_acc.long_message" msgstr "" +"注意:删除用户可能需要一段时间;点击“删除用户”后,请耐心等待,因为用户被删除\n" +" 从我们的系统中,不要离开页面。" #: templates/registration/logged_out.html:4 msgid "registration.logged_out.logged_out_pecha" -msgstr "" +msgstr "登录| Pecha" #: templates/registration/logged_out.html:13 msgid "registration.logged_out.log_back_in" -msgstr "" +msgstr "登录" #: templates/registration/login.html:4 templates/registration/login.html:29 msgid "registration.login.login_in_to_pecha" @@ -756,7 +760,7 @@ msgstr "登录到 Pecha" #: templates/registration/login.html:6 msgid "registration.login.login_in_to_pecha_description" -msgstr "" +msgstr "登录到您的PECHA帐户以制作源表,写笔记并关注其他Pecha用户。" #: templates/registration/login.html:14 msgid "registration.login.you_are_already_logged_in_as" @@ -824,7 +828,8 @@ msgstr "已请求重置此电子邮件地址在 Pecha.org 上的密码。" #: templates/registration/password_reset_email.html:6 #: templates/registration/password_reset_email.txt:5 -msgid "registration.password_reset_email.password_reset_email_to_click_on_link" +msgid "" +"registration.password_reset_email.password_reset_email_to_click_on_link" msgstr "点击此链接,或将其复制到您的浏览器,以选择新密码:" #: templates/registration/password_reset_email.html:12 @@ -834,7 +839,7 @@ msgstr "如果您没有请求重置密码,可以安全地忽略此电子邮件 #: templates/registration/password_reset_email.txt:3 msgid "password.reset_request_made_msg" -msgstr "" +msgstr "已经提出了重置密码的请求" #: templates/registration/password_reset_form.html:4 #: templates/registration/password_reset_form.html:13 @@ -842,8 +847,9 @@ msgid "registration.password_reset_form.forgot_your_password" msgstr "忘记密码" #: templates/registration/password_reset_form.html:6 -msgid "registration.password_reset_form.request_a_link_to_change_your_password" -msgstr "" +msgid "" +"registration.password_reset_form.request_a_link_to_change_your_password" +msgstr "如果您忘记了PECHA密码,请求更改您的PECHA密码。" #: templates/registration/password_reset_form.html:19 msgid "registration.password_reset_form.send_reset_link" @@ -859,15 +865,15 @@ msgstr "Pecha.org 密码重置" #: templates/registration/register.html:4 msgid "registration.register.create_an_account" -msgstr "" +msgstr "创建一个帐户" #: templates/registration/register.html:4 msgid "common.registration.register.pecha" -msgstr "" +msgstr "关闭" #: templates/registration/register.html:6 msgid "registration.register.create_an_account_description" -msgstr "" +msgstr "在Pecha上创建一个帐户,以制作源表,记笔记并关注其他人。" #: templates/registration/register.html:13 msgid "Sign_Up_Title" @@ -883,8 +889,8 @@ msgstr "已有账号?登录" #: templates/static/he/about.html:4 msgid "about.about_pecha" -msgstr "" +msgstr "关于Pecha" #: templates/static/he/about.html:6 msgid "about.about_pecha_description" -msgstr "" +msgstr "Pecha是一个非营利组织,致力于以公开和参与的方式建立佛教学习的未来。" diff --git a/mongo_script.py b/mongo_script.py new file mode 100644 index 0000000000..47330f942c --- /dev/null +++ b/mongo_script.py @@ -0,0 +1,36 @@ +from pymongo import MongoClient + +# Replace the URI string with your MongoDB connection string +connection_string = "mongodb://localhost:27017/" # For a local instance +# For MongoDB Atlas, your connection string might look like: +# connection_string = "mongodb+srv://:@cluster0.mongodb.net/test?retryWrites=true&w=majority" + +# Create a MongoClient +client = MongoClient(connection_string) + +# Access a database +db = client['sefaria'] + +# Access a collection +index_collection = db['index'] + + +documents = index_collection.find() +texts = [] + +for doc in documents: + texts.append({ + 'title': doc['title'], + 'text_category': doc['categories'], + }) + +update_result = db['text_permission_groups'].update_one( + {"name": "default"}, # Query to find the document + {"$push": {"texts": {"$each": texts}}} # Use $push with $each to add multiple items +) + +# Print the result +if update_result.modified_count > 0: + print("Texts added successfully.") +else: + print("No matching document found or no update performed.") \ No newline at end of file diff --git a/reader/templatetags/cache_busting.py b/reader/templatetags/cache_busting.py new file mode 100644 index 0000000000..03f21a5492 --- /dev/null +++ b/reader/templatetags/cache_busting.py @@ -0,0 +1,14 @@ +import os +from django import template +from django.conf import settings + +register = template.Library() + +@register.simple_tag +def versioned_static(file_path): + full_path = os.path.join(settings.BASE_DIR, 'static', file_path) + try: + timestamp = int(os.path.getmtime(full_path)) + return f"{settings.STATIC_URL}{file_path}?v={timestamp}" + except FileNotFoundError: + return f"{settings.STATIC_URL}{file_path}" diff --git a/reader/views.py b/reader/views.py index 48cbbfef47..d88759188d 100644 --- a/reader/views.py +++ b/reader/views.py @@ -6,6 +6,7 @@ import json import urllib.request, urllib.parse, urllib.error from bson.json_util import dumps +from bson import ObjectId import socket import bleach from collections import OrderedDict @@ -76,6 +77,7 @@ from sefaria.settings import NODE_TIMEOUT, DEBUG from sefaria.model.category import TocCollectionNode +from sefaria.system.middleware import get_current_user from sefaria.model.abstract import SluggedAbstractMongoRecord from sefaria.utils.calendars import parashat_hashavua_and_haftara import sefaria.model.story as sefaria_story @@ -158,6 +160,7 @@ def robot(request): def render_template(request, template_name='base.html', app_props=None, template_context=None, content_type=None, status=None, using=None): + """ This is a general purpose custom function that serves to render all the templates in the project and provide a central point for all similar processing. It can take props that are meant for the Node render of ReaderApp (and will properly combine them with base_props() and serialize @@ -330,7 +333,7 @@ def catchall(request, tref, sheet=None): def reader_redirect(uref): # Redirect to standard URLs url = "/" + uref - + print("url>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", url) response = redirect(iri_to_uri(url), permanent=True) params = request.GET.urlencode() response['Location'] += "?%s" % params if params else "" @@ -544,6 +547,10 @@ def make_panel_dicts(oref, versionEn, versionHe, filter, versionFilter, multi_pa Depending on whether `multi_panel` is True, connections set in `filter` are displayed in either 1 or 2 panels. """ panels = [] + # user_email = get_current_user() + # text_list = library.get_text_permission_group(user_email) + # for text in text_list: + # if str(oref) == str(text['title']): # filter may have value [], meaning "all". Therefore we test filter with "is not None". if filter is not None and multi_panel: panels += [make_panel_dict(oref, versionEn, versionHe, filter, versionFilter, "Text", **kwargs)] @@ -552,7 +559,6 @@ def make_panel_dicts(oref, versionEn, versionHe, filter, versionFilter, multi_pa panels += [make_panel_dict(oref, versionEn, versionHe, filter, versionFilter, "TextAndConnections", **kwargs)] else: panels += [make_panel_dict(oref, versionEn, versionHe, filter, versionFilter, "Text", **kwargs)] - return panels @@ -569,7 +575,6 @@ def text_panels(request, ref, version=None, lang=None, sheet=None): ref = '.'.join(map(str, primary_ref.sections)) except InputError: raise Http404 - panels = [] multi_panel = not request.user_agent.is_mobile and not "mobile" in request.GET # Handle first panel which has a different signature in params @@ -1125,6 +1130,7 @@ def _get_user_calendar_params(request): def texts_list(request): + print() title = ("Pecha - Buddhism in your own words") desc = ("The largest free library of Buddhist texts available to read online in Tibetan, English and Chinese including Sutras, Tantras, Abhidharma, Vinaya, commentaries and more.") return menu_page(request, page="navigation", title=title, desc=desc) @@ -1479,7 +1485,6 @@ def protected_post(request): def texts_api(request, tref): oref = Ref.instantiate_ref_with_legacy_parse_fallback(tref) tref = oref.url() - if request.method == "GET": uref = oref.url() if uref and tref != uref: # This is very similar to reader.reader_redirect subfunction, above. @@ -1551,32 +1556,69 @@ def _get_text(oref, versionEn=versionEn, versionHe=versionHe, commentary=comment text["layer"] = [] return text - - if not multiple or abs(multiple) == 1: - text = _get_text(oref, versionEn=versionEn, versionHe=versionHe, commentary=commentary, context=context, - pad=pad, - alts=alts, wrapLinks=wrapLinks, layer_name=layer_name) - return jsonResponse(text, cb) + + text_ref = Ref(tref) + text_title = text_ref.index.get_title("en") + if request.user.is_authenticated: + user_group_text_list = library.get_text_permission_group(request.user.email) + for user_text in user_group_text_list: + if text_title == user_text['title']: + if not multiple or abs(multiple) == 1: + text = _get_text(oref, versionEn=versionEn, versionHe=versionHe, commentary=commentary, context=context, + pad=pad, + alts=alts, wrapLinks=wrapLinks, layer_name=layer_name) + return jsonResponse(text, cb) + else: + # Return list of many sections + assert multiple != 0 + direction = "next" if multiple > 0 else "prev" + target_count = abs(multiple) + + current = 0 + texts = [] + + while current < target_count: + text = _get_text(oref, versionEn=versionEn, versionHe=versionHe, commentary=commentary, context=context, + pad=pad, + alts=alts, wrapLinks=wrapLinks, layer_name=layer_name) + texts += [text] + if not text[direction]: + break + oref = Ref(text[direction]) + current += 1 + return jsonResponse(texts, cb) else: - # Return list of many sections - assert multiple != 0 - direction = "next" if multiple > 0 else "prev" - target_count = abs(multiple) - - current = 0 - texts = [] - - while current < target_count: - text = _get_text(oref, versionEn=versionEn, versionHe=versionHe, commentary=commentary, context=context, - pad=pad, - alts=alts, wrapLinks=wrapLinks, layer_name=layer_name) - texts += [text] - if not text[direction]: - break - oref = Ref(text[direction]) - current += 1 - - return jsonResponse(texts, cb) + email = request.GET.get("email", None) + group_text_list = library.get_text_permission_group(email) + for text in group_text_list: + if text_title == text['title']: + if not multiple or abs(multiple) == 1: + text = _get_text(oref, versionEn=versionEn, versionHe=versionHe, commentary=commentary, context=context, + pad=pad, + alts=alts, wrapLinks=wrapLinks, layer_name=layer_name) + return jsonResponse(text, cb) + else: + # Return list of many sections + assert multiple != 0 + direction = "next" if multiple > 0 else "prev" + target_count = abs(multiple) + + current = 0 + texts = [] + + while current < target_count: + text = _get_text(oref, versionEn=versionEn, versionHe=versionHe, commentary=commentary, context=context, + pad=pad, + alts=alts, wrapLinks=wrapLinks, layer_name=layer_name) + texts += [text] + if not text[direction]: + break + oref = Ref(text[direction]) + current += 1 + return jsonResponse(texts, cb) + + + return jsonResponse({"error": f"You do not have access permission to this text"}, cb) if request.method == "POST": j = request.POST.get("json") @@ -1763,14 +1805,34 @@ def index_api(request, title, raw=False): API for manipulating text index records (aka "Text Info") """ if request.method == "GET": - with_content_counts = bool(request.GET.get("with_content_counts", False)) - i = library.get_index(title).contents(raw=raw, with_content_counts=with_content_counts) - - if request.GET.get("with_related_topics", False): - i["relatedTopics"] = get_topics_for_book(title, annotate=True) - - return jsonResponse(i, callback=request.GET.get("callback", None)) - + if request.user.is_authenticated: + user_group_text_list = library.get_text_permission_group(request.user.email) + for user_text in user_group_text_list: + if title == user_text['title']: + with_content_counts = bool(request.GET.get("with_content_counts", False)) + i = library.get_index(title).contents(raw=raw, with_content_counts=with_content_counts) + + if request.GET.get("with_related_topics", False): + i["relatedTopics"] = get_topics_for_book(title, annotate=True) + + return jsonResponse(i, callback=request.GET.get("callback", None)) + return jsonResponse({"error":f"Either Index is not available OR You are not allowed to access."}, callback=request.GET.get("callback", None)) + else: + email = request.GET.get("email", None) + user_group_text_list = library.get_text_permission_group(email) + for user_text in user_group_text_list: + if title == user_text['title']: + with_content_counts = bool(request.GET.get("with_content_counts", False)) + i = library.get_index(title).contents(raw=raw, with_content_counts=with_content_counts) + + if request.GET.get("with_related_topics", False): + i["relatedTopics"] = get_topics_for_book(title, annotate=True) + + return jsonResponse(i, callback=request.GET.get("callback", None)) + return jsonResponse({"error":f"Either Index is not available OR You are not allowed to access."}, callback=request.GET.get("callback", None)) + + + if request.method == "POST": # use the update function if update is in the params @@ -2314,9 +2376,26 @@ def versions_api(request, tref): API for retrieving available text versions list of a ref. """ oref = Ref(tref) - versions = oref.version_list() + email = request.GET.get("email", None) + versions = [] + if request.user.is_authenticated: + group_text_list = library.get_text_permission_group(request.user.email) + for text in group_text_list: + if str(text['title']) == str(oref): + + versions = oref.version_list() + return jsonResponse(versions, callback=request.GET.get("callback", None)) + return jsonResponse(versions, callback=request.GET.get("callback", None)) - return jsonResponse(versions, callback=request.GET.get("callback", None)) + else: + email = request.GET.get("email", None) + group_text_list = library.get_text_permission_group(email) + for text in group_text_list: + if str(text['title']) == str(oref): + versions = oref.version_list() + return jsonResponse(versions, callback=request.GET.get("callback", None)) + + return jsonResponse(versions, callback=request.GET.get("callback", None)) @catch_error_as_json @@ -2726,13 +2805,63 @@ def _internal_do_post(request, uid): return jsonResponse({"error": "Unsupported HTTP method."}) +def convert_tibetan_text_segment_number(input_string): + + english_numerals = None + # Extract the Tibetan text (non-numeral part) + tibetan_text = re.sub(r'[༠-༩]+[:.,]?[༠-༩]*', '', input_string).strip() + + # Extract the Tibetan numerals (both integer and decimal parts) + tibetan_numerals_match = re.findall(r'[༠-༩]+[:.,]?[༠-༩]*', input_string) + + if tibetan_numerals_match: + tibetan_numerals = tibetan_numerals_match[0] + + # Mapping Tibetan numerals to English numerals + tibetan_to_english = { + '༠': '0', '༡': '1', '༢': '2', '༣': '3', '༤': '4', + '༥': '5', '༦': '6', '༧': '7', '༨': '8', '༩': '9' + } + + # Convert Tibetan numerals to English numerals + english_numerals = ''.join([tibetan_to_english.get(char, char) for char in tibetan_numerals]) + + # Replace Tibetan separators with English decimal point + english_numerals = english_numerals.replace(':', '.').replace(',', '.') + + # Split into integer and decimal parts (if any) + if '.' in english_numerals: + integer_part, decimal_part = english_numerals.split('.') + # Remove leading zeros in the integer part + integer_part = integer_part.lstrip('0') or '0' + # Remove leading zeros in the decimal part + decimal_part = decimal_part.lstrip('0') or '0' + # Combine the parts + english_numerals = f"{integer_part}.{decimal_part}" if decimal_part != '0' else integer_part + else: + # Remove leading zeros in the integer part + english_numerals = english_numerals.lstrip('0') or '0' -def get_name_completions(name, limit, ref_only, topic_override=False): - lang = "he" if has_tibetan(name) else "en" + # Return the Tibetan text and the converted number as a string in a list + return [tibetan_text, english_numerals] + +def get_name_completions(sting_name, limit, ref_only, topic_override=False): + name = sting_name + lang = "he" if has_tibetan(name) else "en" completer = library.ref_auto_completer(lang) if ref_only else library.full_auto_completer(lang) object_data = None ref = None topic = None + + + if lang == "he": + segment = convert_tibetan_text_segment_number(name) + if segment[1]: + enRef = Ref(segment[0]) + name = f"{enRef}.{segment[1]}" + else : + ref = None + if topic_override: topic_set = TopicSet({"titles.text": re.compile(fr'^{re.escape(name)}$', flags=re.IGNORECASE)}, sort=[("numSources", -1)], limit=1) @@ -5060,3 +5189,127 @@ def is_database_reachable(): logger.warn("Failed rollout healthcheck. Healthcheck Response: {}".format(resp)) return http.JsonResponse(resp, status=statusCode) + +@catch_error_as_json +@csrf_exempt +def text_permission_groups_api(request, user_email): + """ + Returns a list of permission groups for the given user. + """ + if request.method == "GET": + user_response = get_user_by_email(user_email) + user_groups = [] + if user_response["status"] == 200: + groups = db.text_permission_groups.find({ + "members": { + "$in": [user_response['data']['email']] + } + }) + for group in groups: + user_groups.append({ + 'name': group["name"], + 'texts': group["texts"], + 'members': group["members"], + 'admin_email': group["admin_email"] + }) + + return jsonResponse({"user_groups":user_groups}) + else: + return jsonResponse(user_response["data"], status=user_response["status"]) + + if request.method == "POST": + try: + data = json.loads(request.body) # Parse the JSON data from the request body + if not data["json"]: + return jsonResponse({"error": "Invalid input JSON"}) + if not request.user.is_authenticated: + key = data['apikey'] + if not key: + return jsonResponse({"error": "You must be logged in or use an API key to save a permission group."}) + apikey = db.apikeys.find_one({"key": key}) + if not apikey: + return jsonResponse({"error": "Unrecognized API key."}) + input_json = data['json'] + return create_group(input_json) + else: + @csrf_protect + def protected_post(request): + input_json = data['json'] + return create_group(input_json) + return protected_post(request) + except json.JSONDecodeError: + return jsonResponse({"error": "Invalid JSON format."}) + + if request.method == "PUT": + try: + update_set = {"$set": {}, "$push": {}} + data = json.loads(request.body) + input_json = data['json'] + + # Update admin_email if provided + if 'admin_email' in input_json: + update_set['$set']["admin_email"] = input_json['admin_email'] + + # Push members if provided + if 'members' in input_json: + update_set['$push']["members"] = {'$each': input_json['members']} + + # Push texts if provided + if 'texts' in input_json: + update_set['$push']["texts"] = {'$each': input_json['texts']} + + # Perform the update operation + result = db.text_permission_groups.update_one({"name": data['name']}, update_set) + # Check if any document was modified + if result.modified_count > 0: + return http.JsonResponse({"message": "Collection updated successfully"}, status=200) + else: + return http.JsonResponse({"error": "No changes made or collection not found"}, status=404) + + except KeyError as e: + return http.JsonResponse({"error": f"Invalid data: {e}"}, status=400) + except Exception as e: + return http.JsonResponse({"error": f"An error occurred: {str(e)}"}, status=500) + + + + +def create_group(input_json): + try: + # Check if a group with the same name already exists + has_group = db.text_permission_groups.find_one({"name": input_json["name"]}) + + if not has_group: + # Create a new group + new_group = { + "id": str(ObjectId()), + "name": input_json["name"], + "texts": input_json.get("texts", []), # Default to empty list if not provided + "members": input_json.get("members", []), # Default to empty list if not provided + "admin_email": input_json["admin_email"] + } + + db.text_permission_groups.insert_one(new_group) + return jsonResponse({"data": "Permission group saved successfully."}, status=200) + + else: + return jsonResponse({"error": "Group already exists."}, status=400) + + except KeyError as e: + return jsonResponse({"error": f"Missing required field: {e.args[0]}"}, status=400) + + except Exception as e: + return jsonResponse({"error": f"An error occurred: {str(e)}"}, status=400) + + +def get_user_by_email(email): + try: + user = User.objects.get(email=email) + user_data = { + "id": user.id, + "email": user.email + } + return {"data": user_data, "status": 200} + except User.DoesNotExist: + return {"data": {"error": f"User:({email}) not found"}, "status": 404} + \ No newline at end of file diff --git a/sefaria/client/wrapper.py b/sefaria/client/wrapper.py index 26e70bb73f..73bbe8d1ab 100644 --- a/sefaria/client/wrapper.py +++ b/sefaria/client/wrapper.py @@ -5,6 +5,8 @@ logger = structlog.get_logger(__name__) from sefaria.model import * +from sefaria.system.middleware import get_current_user +from sefaria.system.database import db from sefaria.datatype.jagged_array import JaggedTextArray from sefaria.system.exceptions import InputError, NoVersionFoundError from sefaria.model.user_profile import user_link, public_user_data @@ -193,7 +195,11 @@ def get_links(tref, with_text=True, with_sheet_links=False): node_depth = getattr(source_ref.index_node, "depth", None) if node_depth is None or len(source_ref.sections) + 1 < node_depth: continue - + + linkPos = (pos + 1) % 2 + linkTref = link.refs[linkPos] + linkRef = Ref(linkTref) + com = format_link_object_for_client(link, False, nRef, pos) except InputError: logger.warning("Bad link: {} - {}".format(link.refs[0], link.refs[1])) @@ -283,7 +289,12 @@ def get_links(tref, with_text=True, with_sheet_links=False): com[versionAttr] = versions com[licenseAttr] = licenses com[vtitleInHeAttr] = versionTitlesInHebrew - links.append(com) + + user_email = get_current_user() + text_list = library.get_text_permission_group(user_email) + for text in text_list: + if linkRef.index.get_title("en") == text['title']: + links.append(com) except NoVersionFoundError as e: logger.warning("Trying to get non existent text for ref '{}'. Link refs were: {}".format(top_nref, link.refs)) continue diff --git a/sefaria/forms.py b/sefaria/forms.py index 8d7ac00474..c01dbaf6bb 100644 --- a/sefaria/forms.py +++ b/sefaria/forms.py @@ -38,8 +38,9 @@ class SefariaDeleteSheet(forms.Form): class SefariaLoginForm(EmailAuthenticationForm): - email = forms.EmailField(max_length=75, widget=forms.EmailInput(attrs={'placeholder': _("placeholder_email_address")})) - password = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': _("placeholder_password")})) + email = forms.EmailField(max_length=75, widget=forms.EmailInput(attrs={'placeholder': _("placeholder_email_address"),'class': 'hiii'})) + password = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': _("placeholder_password"),'class': 'form-input monlam-font' +})) class SefariaNewUserForm(EmailUserCreationForm): diff --git a/sefaria/model/category.py b/sefaria/model/category.py index 9339521c07..2913b33800 100644 --- a/sefaria/model/category.py +++ b/sefaria/model/category.py @@ -9,6 +9,7 @@ from . import schema as schema from . import text as text from . import collection as collection +from sefaria.system.middleware import get_current_user class Category(abstract.AbstractMongoRecord, schema.AbstractTitledOrTermedObject): @@ -48,7 +49,10 @@ def _set_derived_attributes(self): # which should then propagate to the `lastPath` and `sharedTitle` self.change_key_name(self.path[-1]) self._load_title_group() - + + def get_category_path(self): + return self.path + def change_key_name(self, name): # Doesn't yet support going from shared term to local or vise-versa. if self.sharedTitle and schema.Term().load({"name": name}): @@ -310,13 +314,24 @@ def _make_index_node(self, index, old_title=None, mobile=False): } return TocTextIndex(d, index_object=index) - + def _add_category(self, cat): try: + from sefaria.model import library tc = TocCategory(category_object=cat) - parent = self._path_hash[tuple(cat.path[:-1])] if len(cat.path[:-1]) else self._root - parent.append(tc) - self._path_hash[tuple(cat.path)] = tc + user_email = get_current_user() + all_cats = [] # store previously loaded path + text_list = library.get_text_permission_group(user_email) + for c in text_list: + cat_subsets = [c['category'][:i] for i in range(1, len(c['category']) + 1)] + if cat.path in cat_subsets and cat.path not in all_cats: + all_cats.append(cat.path) + parent = self._path_hash[tuple(cat.path[:-1])] if len(cat.path[:-1]) else self._root + parent.append(tc) + self._path_hash[tuple(cat.path)] = tc + # parent = self._path_hash[tuple(cat.path[:-1])] if len(cat.path[:-1]) else self._root + # parent.append(tc) + # self._path_hash[tuple(cat.path)] = tc except KeyError: logger.warning(f"Failed to find parent category for {'/'.join(cat.path)}") @@ -504,6 +519,7 @@ def __init__(self, serial=None, **kwargs): self.order = self._index_object.order[0] def get_index_object(self): + print("index >>>>>>>>>>>>>>>>>>>>>>>>", self._index_object) return self._index_object optional_param_keys = [ diff --git a/sefaria/model/schema.py b/sefaria/model/schema.py index 997e7e18e3..abac3d63fb 100644 --- a/sefaria/model/schema.py +++ b/sefaria/model/schema.py @@ -1052,7 +1052,7 @@ def full_regex(self, title, lang, anchored=True, compiled=True, capture_title=Fa """ parentheses = kwargs.get("parentheses", False) - prefixes = 'בכ|וב|וה|וכ|ול|ומ|וש|כב|ככ|כל|כמ|כש|לכ|מב|מה|מכ|מל|מש|שב|שה|שכ|של|שמ|ב|כ|ל|מ|ש|ה|ו|ד' if lang == 'he' else '' + prefixes = '' if lang == 'he' else '' prefix_group = rf'(?:{prefixes})?' key = (title, lang, anchored, compiled, kwargs.get("for_js"), kwargs.get("match_range"), kwargs.get("strict"), kwargs.get("terminated"), kwargs.get("escape_titles"), parentheses) @@ -2599,7 +2599,8 @@ def _core_regex(self, lang, group_id=None, **kwargs): if lang == "en": reg += r"\d+)" elif lang == "he": - reg += self.hebrew_number_regex() + r")" + # reg += self.hebrew_number_regex() + r")" + reg += r"\d+)" # todo: implement tibetan number regex return reg diff --git a/sefaria/model/text.py b/sefaria/model/text.py index a91731b740..dc649d4790 100644 --- a/sefaria/model/text.py +++ b/sefaria/model/text.py @@ -1927,7 +1927,6 @@ def save(self, force_save=False): ) else: self.full_version = Version().load({"title": self._oref.index.title, "language": self.lang, "versionTitle": self.vtitle}) - print("version:: >>>>>>>>>>>>", self.full_version) 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 @@ -5078,7 +5077,7 @@ def set_last_cached_time(self): self.last_cached = time.time() # just use the unix timestamp, we dont need any fancy timezone faffing, just objective point in time. scache.set_shared_cache_elem("last_cached", self.last_cached) - def get_toc(self, rebuild=False): + def get_toc(self, rebuild=True): """ Returns the ToC Tree from the cache, DB or by generating it, as needed. """ @@ -5091,7 +5090,7 @@ def get_toc(self, rebuild=False): self.set_last_cached_time() return self._toc - def get_toc_json(self, rebuild=False): + def get_toc_json(self, rebuild=True): """ Returns as JSON representation of the ToC. This is generated on Library start up as an optimization for the API, to allow retrieval of the data with a single call. @@ -5105,7 +5104,7 @@ def get_toc_json(self, rebuild=False): self.set_last_cached_time() return self._toc_json - def get_toc_tree(self, rebuild=False, mobile=False): + def get_toc_tree(self, rebuild=True, mobile=False): """ :param mobile: (Aug 30, 2021) Added as a patch after navigation redesign launch. Currently only adds 'firstSection' to toc for mobile export. This field is no longer required on prod but is still required @@ -5116,6 +5115,83 @@ def get_toc_tree(self, rebuild=False, mobile=False): self._toc_tree = TocTree(self, mobile=mobile) self._toc_tree_is_ready = True return self._toc_tree + + def get_text_permission_group(self, user_email=None): + if user_email: + groups = db.text_permission_groups.find({ + "members": { + "$in": [user_email] + } + }) + text_list = self.get_user_group_text(groups) + return text_list + else: + text_list = self.get_text_in_default_group() + return text_list + + def get_user_group_text(self, groups=[]): + # Track text occurrences and details across all groups + text_counts = {} + text_details = {} + + for group in groups: + for text in group['texts']: + title = text['title'] + categories = text['text_category'] + + # Update text_counts and store details + text_counts[title] = text_counts.get(title, 0) + 1 + if title not in text_details: + text_details[title] = categories + + # Extract unique texts (appear exactly once) with their categories + unique_text_categories = [ + { + "title": title, + "category": text_details[title] + } + for title, count in text_counts.items() if count == 1 + ] + + # Collect non-duplicate categories for texts appearing more than once + other_text_categories = [ + { + "title": title, + "category": text_details[title] + } + for title, count in text_counts.items() if count > 1 + ] + + # Combine both lists + combined_categories = unique_text_categories + other_text_categories + + return combined_categories + + def get_text_in_default_group(self): + groups = db.text_permission_groups.find() + # Separate texts in "default" group and others + default_texts = {} + other_texts = set() + for group in groups: + for text in group['texts']: + title = text['title'] + categories = text['text_category'] + + if group["name"] == "default": + default_texts[title] = categories + + else: + other_texts.add(title) + # Find texts unique to the "default" group + unique_to_default = [ + { + "title": title, + "category": categories + } + for title, categories in default_texts.items() if title not in other_texts + ] + return unique_to_default + def get_topic_toc(self, rebuild=False): """ diff --git a/sefaria/settings.py b/sefaria/settings.py index adbba0fe1c..7a6fe4bdab 100644 --- a/sefaria/settings.py +++ b/sefaria/settings.py @@ -139,6 +139,7 @@ 'sefaria.system.middleware.ProfileMiddleware', 'sefaria.system.middleware.CORSDebugMiddleware', 'sefaria.system.middleware.SharedCacheMiddleware', + 'sefaria.system.middleware.CurrentUserMiddleware', 'sefaria.system.multiserver.coordinator.MultiServerEventListenerMiddleware', 'django_structlog.middlewares.RequestMiddleware', #'easy_timezones.middleware.EasyTimezoneMiddleware', diff --git a/sefaria/system/database.py b/sefaria/system/database.py index 7f1529942d..084174b756 100644 --- a/sefaria/system/database.py +++ b/sefaria/system/database.py @@ -54,6 +54,11 @@ def ensure_indices(active_db=None): ('groups', ["privateSlug"], {'unique': True}), ('groups', ["members"], {}), ('groups', ["admins"], {}), + ('text_permission_groups', ["name"], {'unique': True}), + ('text_permission_groups', ["id"], {'unique': True}), + ('text_permission_groups', ["texts"], {}), + ('text_permission_groups', ["members"], {}), + ('text_permission_groups', ["admin"], {}), ('history', ["revision"], {}), ('history', ["method"], {}), ('history', [[("ref", pymongo.ASCENDING), ("version", diff --git a/sefaria/system/middleware.py b/sefaria/system/middleware.py index 4849f195dc..1fe9274ffe 100644 --- a/sefaria/system/middleware.py +++ b/sefaria/system/middleware.py @@ -3,6 +3,7 @@ import cProfile import pstats from io import StringIO +import threading from django.conf import settings from django.utils import translation @@ -16,6 +17,7 @@ from sefaria.system.cache import get_shared_cache_elem, set_shared_cache_elem from django.utils.deprecation import MiddlewareMixin +_thread_local = threading.local() class SharedCacheMiddleware(MiddlewareMixin): def process_request(self, request): @@ -170,6 +172,32 @@ def current_domain_lang(request): return domain_lang + +class CurrentUserMiddleware: + """ + Middleware to store the current user in thread-local storage for global access. + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if request.user.is_authenticated: + _thread_local.user = request.user.email + else: + _thread_local.__dict__.clear() + # Set the current user in thread-local storage + response = self.get_response(request) + + return response + +def get_current_user(): + """ + Retrieve the current user from thread-local storage. + """ + return getattr(_thread_local, 'user', None) + + class CORSDebugMiddleware(MiddlewareMixin): def process_response(self, request, response): if DEBUG: diff --git a/sefaria/urls.py b/sefaria/urls.py index 1c8ecf2baf..c4ac519034 100644 --- a/sefaria/urls.py +++ b/sefaria/urls.py @@ -195,6 +195,8 @@ url(r'^api/site_stats/?$', reader_views.site_stats_api), url(r'^api/manuscripts/(?P.+)', reader_views.manuscripts_for_source), url(r'^api/background-data', reader_views.background_data_api), + url(r'^api/text_permission_groups/?(?P.+)?$', reader_views.text_permission_groups_api), + ] diff --git a/sefaria/views.py b/sefaria/views.py index f6335824e5..4b7c435dc4 100644 --- a/sefaria/views.py +++ b/sefaria/views.py @@ -89,6 +89,13 @@ def process_register_form(request, auth_method='session'): password=form.cleaned_data['password1']) profile = UserProfile(id=user.id, user_registration=True) user_type = form.cleaned_data['user_type'] + # Add user to group ("dafault") + update_operation = { + "$push": { + "members": form.cleaned_data["email"] + } + } + db.text_permission_groups.update_one({ "name": "default" }, update_operation) # add analytics add_signup_info(email=profile.email,first_name=profile.first_name,last_name=profile.last_name) profile.assign_slug() @@ -146,7 +153,6 @@ def register(request): if request.method == 'POST': errors, _, form = process_register_form(request) - print("form >>>>>>>>>>>>>>>>>", form) if len(errors) == 0: if "noredirect" in request.POST: return HttpResponse("ok") diff --git a/static/css/common.css b/static/css/common.css index e95bfc2616..7fccf1e84b 100644 --- a/static/css/common.css +++ b/static/css/common.css @@ -747,7 +747,7 @@ a:active { border: none; color: #666; font-size: 18px; - font-family: ; + font-family: 'Monlam Uni OuChan2'; background-color: white; -webkit-appearance: none; } diff --git a/static/css/s2.css b/static/css/s2.css index 4a786c29d9..69814138f8 100644 --- a/static/css/s2.css +++ b/static/css/s2.css @@ -233,6 +233,10 @@ a, a:hover { --inline-link-red: #cc5454; } +.back-button-icon { + font-size: 16px; /* Adjust the size as needed */ + } + /* Font Family */ body, .sans-serif { --english-font: var(--english-sans-serif-font-family); @@ -1899,7 +1903,8 @@ div.interfaceLinks-row a { .readerContent .readerError .readerErrorText { padding-top: 20px; - font-size: .8em; + font-size: 1.5em; + color:red; } .textColumn { @@ -2882,7 +2887,7 @@ span .asterisk { } .readerTocTopics .topicTitle .int-he { - font-family: "Taamey Frank", "adobe-garamond-pro", "Crimson Text", Georgia, "Times New Roman", serif; + font-family: var(--hebrew-serif-font-family); } .readerTocTopics .topicDescription.systemText .int-en, @@ -2926,7 +2931,7 @@ span .asterisk { } .topicPanel .resourcesLink.blue .int-he { - font-family: "Taamey Frank", "adobe-garamond-pro", "Crimson Text", Georgia, "Times New Roman", serif; + font-family: var(--hebrew-serif-font-family); margin-bottom: -3px; } @@ -3064,7 +3069,7 @@ h1 div.languageToggle .en { } h1 .languageToggle .he { - font-family: "adobe-garamond-pro", "Crimson Text", Georgia, serif; + font-family: var(--hebrew-serif-font-family); font-size: 22px; } @@ -3492,6 +3497,7 @@ a.navBlockTitle:hover { font-size: 24px; color: var(--medium-grey); font-size: 14px; + font-family: var(--hebrew-serif-font-family); } .communityPage .recentlyPublished { @@ -4689,6 +4695,7 @@ details .open-details::before { .versionBlock .versionDetails .versionDetailsLabel, .versionBlock .versionDetails .versionDetailsLabel:hover { text-decoration: none; + font-family: var(--hebrew-serif-font-family); } .versionBlock .versionDetails .versionBuyImage .versionDetailsImageLink img { @@ -8780,7 +8787,7 @@ But not to use a display block directive that might break continuous mode for ot box-sizing: border-box; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); font-size: 18px; - font-family: "adobe-garamond-pro", "Crimson Text", Georgia, serif; + font-family: var(--hebrew-serif-font-family); margin-bottom: 10px } @@ -10093,6 +10100,7 @@ body.interface-hebrew .publishBox textarea::placeholder { box-sizing: border-box; box-shadow: none; padding: 9px 10px; + white-space: nowrap; } .publishBox .react-tags__suggestions ul { @@ -10162,10 +10170,10 @@ body.interface-english .publishBox .react-tags__suggestions ul { box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); } -.interface-hebrew .editorSidebarToggle { +/* .interface-hebrew .editorSidebarToggle { left: 30px; right: auto; -} +} */ .editorSidebarToggle:active { @@ -11183,7 +11191,7 @@ body #keyboardInputMaster tbody tr td table tbody tr td.pressed { } .readerPanel.hebrew .addToSourceSheetBox .selectedRef span { - font-family: "Taamey Frank", "adobe-garamond-pro", "Crimson Text", Georgia, "Times New Roman", serif; + font-family: var(--hebrew-serif-font-family), "Taamey Frank", "adobe-garamond-pro", "Crimson Text", Georgia, "Times New Roman", serif; font-size: 1.2em; } @@ -11696,6 +11704,7 @@ span.purim-emoji img { width: 100%; box-sizing: border-box; font-size: 2.2em; + font-family: var(--hebrew-serif-font-family); line-height: 1.6; text-align: justify; background-color: inherit; @@ -12158,6 +12167,7 @@ span.purim-emoji img { } .addInterfaceInput { + padding: 10px; pointer-events: none; display: inline-block; } @@ -12212,6 +12222,7 @@ span.purim-emoji img { .addInterfaceInput select.suggestionBox { overflow: scroll; font-size: 22px; + pointer-events: auto; } @@ -12494,16 +12505,20 @@ section.SheetOutsideBiText { .bilingual .SheetOutsideBiText:before, .bilingual .SheetSource:before { content: counter(css-counter); - right: -34px; + left: -34px; font-family: var(--english-sans-serif-font-family); } +@counter-style tibetan-numbers { + system: numeric; + symbols: '༠' '༡' '༢' '༣' '༤' '༥' '༦' '༧' '༨' '༩'; +} .hebrew .SheetOutsideBiText:before, .hebrew .SheetSource:before { - content: counter(css-counter, hebrew); - right: -34px; + content: counter(css-counter, tibetan-numbers); + left: -34px; font-family: var(--hebrew-sans-serif-font-family); } @@ -13173,8 +13188,8 @@ section.SheetSource .SheetOutsideBiText { font-family: var(--hebrew-serif-font-family); } -.bo-date { - font-family: var(--hebrew-sans-serif-font-family); +.hi-date { + font-family: var(--hebrew-serif-font-family); } .sheetList .sheet .sheetTags .bullet { @@ -14558,6 +14573,10 @@ body .homeFeedWrapper.userStats { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); } +.user_state { + font-family: var(--hebrew-serif-font-family); +} + .userStatModeButton { background-color: #fff; color: #666; diff --git a/static/icons/warning-sign.svg b/static/icons/warning-sign.svg new file mode 100644 index 0000000000..cee882f5f2 --- /dev/null +++ b/static/icons/warning-sign.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/js/AboutSheet.jsx b/static/js/AboutSheet.jsx index 1f68b53843..19090a6489 100644 --- a/static/js/AboutSheet.jsx +++ b/static/js/AboutSheet.jsx @@ -295,8 +295,8 @@ const AboutSheet = ({ masterPanelSheetId, toggleSignUpModal }) => {
- {Sefaria.hebrew.tibetanNumeral(sheet.views)} - {sheetSaves.length} {Sefaria._("common.saves")} + {Sefaria.interfaceLang == 'hebrew'? Sefaria.hebrew.tibetanNumeral(sheet.views) : sheet.views} + {Sefaria.interfaceLang == 'hebrew'? Sefaria.hebrew.tibetanNumeral(sheetSaves.length ) : sheetSaves.length } {Sefaria._("common.saves")}
{/* {sheet.status !== 'public' ? (
{Sefaria._("profile.tab.sheet.tag.not_published")}
) : undefined} */} diff --git a/static/js/BookPage.jsx b/static/js/BookPage.jsx index 261c76cf98..72713491af 100644 --- a/static/js/BookPage.jsx +++ b/static/js/BookPage.jsx @@ -92,7 +92,6 @@ class BookPage extends Component { loadData() { // Ensures data this text is in cache, rerenders after data load if needed Sefaria.getIndexDetails(this.props.title).then(data => this.setState({indexDetails: data})); - if (this.isBookToc() && !this.props.compare) { if(!this.state.versionsLoaded){ Sefaria.getVersions(this.props.title).then(result => { @@ -175,7 +174,7 @@ class BookPage extends Component { const heTitle = index ? index.heTitle : title; const category = this.props.category; const isDictionary = this.state.indexDetails && !!this.state.indexDetails.lexiconName; - const categories = Sefaria.index(this.props.title).categories; + const categories = Sefaria.index(this.props.title)? Sefaria.index(this.props.title).categories : []; let currObjectVersions = this.state.currObjectVersions; let catUrl; if (category == "Commentary") { diff --git a/static/js/ConnectionFilters.jsx b/static/js/ConnectionFilters.jsx index 2198a0f888..89e6c58dd4 100644 --- a/static/js/ConnectionFilters.jsx +++ b/static/js/ConnectionFilters.jsx @@ -70,7 +70,9 @@ class CategoryFilter extends Component {
- + + + ({this.props.count}) diff --git a/static/js/Editor.jsx b/static/js/Editor.jsx index 51ee0153f6..24a7480031 100644 --- a/static/js/Editor.jsx +++ b/static/js/Editor.jsx @@ -939,7 +939,7 @@ const AddInterfaceInput = ({ inputType, resetInterface }) => { getSuggestions={getSuggestions} inputValue={inputValue} changeInputValue={setInputValue} - inputPlaceholder="Search for a Text or Commentator." + inputPlaceholder={Sefaria._('sheet.editor.source.input_field.placeholder')} buttonTitle="Add Source" autocompleteClassNames="addInterfaceInput" showSuggestionsOnSelect={true} diff --git a/static/js/Misc.jsx b/static/js/Misc.jsx index 0e76dcc0e9..afd09a1ef1 100644 --- a/static/js/Misc.jsx +++ b/static/js/Misc.jsx @@ -93,7 +93,6 @@ const InterfaceText = ({text, html, markdown, children, placeholder, disallowedM if (chlCount === 1) { // Same as passing in a `en` key but with children syntax if (placeholder) { textResponse = t(children, placeholder) - console.log(textResponse) } else { textResponse = t(children) } @@ -790,8 +789,7 @@ class TextBlockLink extends Component { } const subtitle = displayValue ? ( - {displayValue} - {heDisplayValue} + {Sefaria.interfaceLang != 'hebrew' ? displayValue: heDisplayValue} ) : null; @@ -829,8 +827,7 @@ class TextBlockLink extends Component { } return ( - {title} - {heTitle} + {Sefaria.interfaceLang != 'hebrew'? title: heTitle } {subtitle} ); @@ -1370,6 +1367,7 @@ class CloseButton extends Component { } + class DisplaySettingsButton extends Component { render() { let style = this.props.placeholder ? {visibility: "hidden"} : {}; @@ -2033,6 +2031,37 @@ LoginPrompt.propTypes = { fullPanel: PropTypes.bool, }; +class MessageModel extends Component { + render() { + return ( + this.props.show ?
+
+
+
+

+ {this.props.errorType} +

+
+ { this.props.message } +
+ + model.message_model.explore_other_text + +
+
+
: null + ) + } +} + +MessageModel.propTypes = { + errorType: PropTypes.string, + show: PropTypes.bool, + onClose: PropTypes.func.isRequired, + message: PropTypes.string, +} + + class SignUpModal extends Component { render() { let modalContent = !this.props.modalContentKind ? generateContentForModal() : generateContentForModal(this.props.modalContentKind); @@ -3186,6 +3215,7 @@ const Autocompleter = ({getSuggestions, showSuggestionsOnSelect, inputPlaceholde const generatePreviewText = (ref) => { + console.log("getText", ref) Sefaria.getText(ref, {context:1, stripItags: 1}).then(text => { let segments = Sefaria.makeSegments(text, true); segments = Sefaria.stripImagesFromSegments(segments); @@ -3348,6 +3378,7 @@ export { SheetMetaDataBox, SheetAuthorStatement, SheetTitle, + MessageModel, InterfaceLanguageMenu, Autocompleter, DonateLink, diff --git a/static/js/ReaderApp.jsx b/static/js/ReaderApp.jsx index a0709df5e4..ac30634500 100644 --- a/static/js/ReaderApp.jsx +++ b/static/js/ReaderApp.jsx @@ -28,6 +28,7 @@ import { } from './StaticPages'; import { SignUpModal, + MessageModel, InterruptingMessage, Banner, CookiesNotification, @@ -1074,7 +1075,7 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { } openURL(href, replace=true, overrideContentLang=false) { // Attempts to open `href` in app, return true if successful. - href = href.startsWith("/") ? "https://www.sefaria.org" + href : href; + href = href.startsWith("/") ? "https://www.pecha.org" + href : href; let url; try { url = new URL(href); @@ -1083,7 +1084,7 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { } // Open non-Sefaria urls in new tab/window // TODO generalize to any domain of current deploy. - if (url.hostname.indexOf("www.sefaria.org") === -1) { + if (url.hostname.indexOf("www.pecha.org") === -1) { window.open(url, '_blank') return true; } @@ -1108,6 +1109,7 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { this.showSaved(); } else if (path.match(/\/texts\/.+/)) { + console.log("my",path) this.showLibrary(path.slice(7).split("/")); } else if (path === "/collections") { @@ -2070,7 +2072,7 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { var panels = []; var allOpenRefs = panelStates.filter( panel => panel.mode == "Text" && !panel.menuOpen) .map( panel => Sefaria.humanRef(panel.highlightedRefs.length ? panel.highlightedRefs : panel.refs)); - + for (var i = 0; i < panelStates.length; i++) { const panel = this.clonePanel(panelStates[i]); if (!("settings" in panel )) { debugger; } @@ -2184,7 +2186,12 @@ toggleSignUpModal(modalContentKind = SignUpModalKind.Default) { (
{panels}
) : null; - + // const signUpModal = ( text.error.message.text_not_accessible + } else { + return {error} + } + } + render() { + console.log("book ref:",Sefaria.index(this.state.bookRef)) if (this.state.error) { return (
- Something went wrong! Please use the back button or the menus above to get back on track. +
+ text.error.message.something_went_wrong
- Error Message: - {this.state.error} + {this.errorMessage(this.state.error)}
); } + if(this.state.bookRef !=null && !Sefaria.index(this.state.bookRef)) { + return ( + + ) + } + let items = []; let menu = null; let isNarrowColumn = false; diff --git a/static/js/SourceEditor.jsx b/static/js/SourceEditor.jsx index dd5ce3b90f..9d50033d65 100644 --- a/static/js/SourceEditor.jsx +++ b/static/js/SourceEditor.jsx @@ -101,7 +101,7 @@ const SourceEditor = ({topic, close, origData={}}) => { getSuggestions={getSuggestions} inputValue={displayRef} changeInputValue={handleChange} - inputPlaceholder="Search for a Text or Commentator." + inputPlaceholder={Sefaria._('sheet.editor.source.input_field.placeholder')} buttonTitle="Select Source" autocompleteClassNames="addInterfaceInput" showSuggestionsOnSelect={true} diff --git a/static/js/TextCategoryPage.jsx b/static/js/TextCategoryPage.jsx index 5d2c6b21bd..069e8f268a 100644 --- a/static/js/TextCategoryPage.jsx +++ b/static/js/TextCategoryPage.jsx @@ -292,7 +292,6 @@ const MenuItem = ({href, nestLevel, title, heTitle, cats, onClick, enDesc, heDes const TextMenuItem = ({item, categories, nestLevel, onClick}) => { const [title, heTitle] = getRenderedTextTitleString(item.title, item.heTitle, categories); - console.log("title : ", title, heTitle) return ( ( const UserStatModeButton = ({thisMode, activeMode, setMode}) => (
setMode(thisMode)}> - {Sefaria._(thisMode)} + {Sefaria._(thisMode)}
); @@ -130,7 +130,7 @@ const UserDataBlock = ({user_data, site_data}) => ( const OverallActivityBlock = ({user_data}) => (

- "profile.user_state.overall_activities + profile.user_state.overall_activities

@@ -175,8 +175,7 @@ const YourFavoriteTextsBlock = ({user_data}) => ( user_data.mostViewedRefs.length ?

- Your Favorite Texts - ཁྱེད་རང་དགའ་ཤོས་ཀྱི་ཡིག་ཆ། + profile.buddhist_tracker.favorite_texts

)}/> @@ -187,8 +186,7 @@ const YourFavoriteSheetsBlock = ({user_data}) => ( user_data.mostViewedSheets.length ?

- Your Favorite Sheets - דפי מקורות מועדפים + profile.buddhist_tracker.favorite_sheets

diff --git a/static/js/sefaria/i18n.js b/static/js/sefaria/i18n.js index 6da194267c..672f38112e 100644 --- a/static/js/sefaria/i18n.js +++ b/static/js/sefaria/i18n.js @@ -4,15 +4,12 @@ import { initReactI18next } from 'react-i18next' import Sefaria from './sefaria' import LanguagesJson from './localizationLanguage/combineSring' -console.log("ang: ", Sefaria.interfaceLang) - const langs = { hebrew: "bo", chinese: "zh", english: "en" } -let current_lang = i18n .use(Languagedetector) .use(initReactI18next) diff --git a/static/js/sefaria/localizationLanguage/chinese.json b/static/js/sefaria/localizationLanguage/chinese.json index f23bd450dd..9d0cedc4b0 100644 --- a/static/js/sefaria/localizationLanguage/chinese.json +++ b/static/js/sefaria/localizationLanguage/chinese.json @@ -168,10 +168,15 @@ "feedback.request_feature": "请求功能", "feedback.give_thanks": "表示感谢", "feedback.other": "其他", + "text.error.message.something_went_wrong": "發生錯誤!請使用返回按鈕或上方選單繼續操作。", + "text.error.message.text_not_accessible": "您無權存取此文字", "feedback.please_select_type": "请选择反馈类型", "feedback.message.error_sending_feedback": "很遗憾,发送反馈时发生错误。请重试或重新加载此页面。", "common.select_type": "选择类型", "sheet.source_sheet.added_by": "由...添加", + "model.message_model.explore_other_text": "探索其他文本", + "model.message_model.message_type": "警告", + "model.message_model.message": "文本不可用或您无权访问。", "modal.sign_up.default.love_learning":"སློབ་གཉེར་ལ་དགའ་བོ་ཡོད་དམ།", "modal.sign_up.default.sign_up__to_get_more_from_sefaria":"", "model.sign_up.add_connection.connection_to_another_text": "", @@ -256,6 +261,8 @@ "header.site_language": "网站语言", "header.profileMenu.profile": "个人资料", "header.profileMenu.create_New_Sheet": "创建新表单", + "profile.buddhist_tracker.favorite_texts": "您最喜爱的经文", + "profile.buddhist_tracker.favorite_sheets": "您最喜爱的表格", "header.profileMenu.account_settings": "账户设置", "header.profileMenu.log_out": "登出", "profile.tab.collection.description": "您可以使用收藏来整理您的表单或公开您喜欢的表单。收藏可以私下分享或在Sefaria上公开", @@ -349,6 +356,7 @@ "text.don’t_lose_that_thought": "别丢了那个想法!", "text.create_free_account_to_do_more_on_Pecha": "创建一个免费账户以在 Pecha 上做更多事", "text.take_notes_on_this_text": "在此文本上做笔记", + "text.root_text": "根文本", "text.get_updates_on_new_features": "获取新功能的更新", "text.citing": "引用", "text.sites_that_are_listed_here_use_the": "这里列出的网站使用", @@ -466,6 +474,7 @@ "sheet.box_sources": "框选资源", "sheet.sheet_language": "表单语言", "sheet.editor.write_something": "Write something...", + "sheet.editor.source.input_field.placeholder": "搜尋文字或註解者。", "sheet.sheet_layout": "表单布局", "sheet.stacked": "堆叠", "sheet.side_by_side": "并排", diff --git a/static/js/sefaria/localizationLanguage/english.json b/static/js/sefaria/localizationLanguage/english.json index b1246e7b89..c8bea4292f 100644 --- a/static/js/sefaria/localizationLanguage/english.json +++ b/static/js/sefaria/localizationLanguage/english.json @@ -147,7 +147,7 @@ "side_nav.updates":"Updates", "sheet.create_new": "Create New", "sheet.publish_setting": "Publish Settings", - "published":"Publish Settings", + "sheet.published":"Published", "text.reader_option_menu.color":"Color", "text.reader_option_menu.font_size":"Font Size", "text.reader_option_menu.punctuation":"Punctuation", @@ -167,9 +167,14 @@ "feedback.give_thanks":"Give thanks", "feedback.other":"Other", "feedback.please_select_type":"Please select a feedback type", + "text.error.message.something_went_wrong": "Something went wrong! Please use the back button or the menus above to get back on track.", + "text.error.message.text_not_accessible": " You do not have access permission to this text", "feedback.message.error_sending_feedback":"Unfortunately, there was an error sending this feedback. Please try again or try reloading this page.", "common.select_type":"Select Type", "sheet.source_sheet.added_by":"Added by", + "model.message_model.explore_other_text": "Explore other Texts", + "model.message_model.message_type": "Warning", + "model.message_model.message": "Text is not available or you do not have permission to access it.", "modal.sign_up.default.love_learning":"Love Learning?", "modal.sign_up.default.sign_up__to_get_more_from_sefaria":"Sign up to get more from Pecha", "model.sign_up.add_connection.connection_to_another_text": "Want to document a connection to another text?", @@ -256,6 +261,8 @@ "header.profileMenu.create_New_Sheet": "Create a New Sheet", "header.profileMenu.account_settings": "Account Settings", "header.profileMenu.log_out": "Logout", + "profile.buddhist_tracker.favorite_texts": "Your Favorite Texts", + "profile.buddhist_tracker.favorite_sheets": "Your Favorite Sheets", "profile.tab.collection.description": "You can use collections to organize your sheets or public sheets you like. Collections can be shared privately or made public on Pecha", "profile.buddhish_text_tracker":"Buddhist Text Tracker", "profile.previous_year":"Previous Year", @@ -349,6 +356,7 @@ "text.don’t_lose_that_thought": "Don’t lose that thought!", "text.create_free_account_to_do_more_on_Pecha": "Create a free account to do more on Pecha", "text.take_notes_on_this_text": "Take notes on this text", + "text.root_text": "Root Text", "text.get_updates_on_new_features": "Get updates on new features", "text.citing":"Citing", "text.sites_that_are_listed_here_use_the":"Sites that are listed here use the", @@ -466,6 +474,7 @@ "sheet.format": "Format", "sheet.number_sources": "Number Sources", "sheet.box_sources": "Box Sources", + "sheet.editor.source.input_field.placeholder": "Search for a Text or Commentator.", "sheet.sheet_language": "Sheet Language", "sheet.sheet_layout": "Sheet Layout", "sheet.stacked": "Stacked", diff --git a/static/js/sefaria/localizationLanguage/tibetan.json b/static/js/sefaria/localizationLanguage/tibetan.json index 7172f159ff..27dfbb65fe 100644 --- a/static/js/sefaria/localizationLanguage/tibetan.json +++ b/static/js/sefaria/localizationLanguage/tibetan.json @@ -147,7 +147,7 @@ "side_nav.updates":"རིམ་སྤར།", "sheet.create_new": "གསར་པ་བཟོ།", "sheet.publish_setting": "སྤེལ་གྱི་སྒྲིག་འགོད།", - "sheet.published": "", + "sheet.published": "འབྲི་བཞག", "text.reader_option_menu.color":"ཚོན་མདོག", "text.reader_option_menu.font_size":"ཡིག་གཟུགས་ཆེ་ཆུང་།", "text.reader_option_menu.punctuation":"ཚེག་ཤད།", @@ -168,11 +168,16 @@ "feedback.request_feature":"མཉེན་ཆས་ཀྱི་བྱེད་ནུས་གསར་པ་དགོས་མཁོ།", "feedback.give_thanks":"ཐུགས་རྗེ་ཆེ་ཞུ་བ།", "feedback.other":"གནས་ཚུལ་གཞན།", + "text.error.message.something_went_wrong": "ཅི་རིགས་ནོར་བ་བྱུང་འདུག། ཕྱིར་འདྲེན་མ་ལག་གམ་སྟེང་གི་འགུལ་བཞུད་ཀྱི་ཐབས་ལམ་བེད་སྤྱོད་གྱིས།", + "text.error.message.text_not_accessible": "ཁྱེད་ལ་བརྡ་ཡིག་འདི་ལ་ཐོབ་ཐང་མེད།", "feedback.please_select_type":"བསམ་འཆར་གྱི་རིགས་ཤིག་འདེམ་རོགས།", "feedback.message.error_sending_feedback":"སྟབས་མ་ལེགས་པ་ཞིག་ལ་བསམ་འཆར་འདི་གཏོང་བ་ལ་གནད་དོན་ཞིག་བྱུང་སོང་། ཤོག་ངོས་འདི་བསྐྱར་ལེན་ནམ་ཡང་ན་བསྐྱར་དུ་ཚོད་ལྟ་ཞིག་བྱེད་རོགས།", "common.select_type":"རིགས་དབྱེ།", "sheet.source_sheet.added_by":"ཡིས་བསྣན་འདུག", "modal.sign_up.default.love_learning":"སློབ་གཉེར་ལ་དགའ་བོ་ཡོད་དམ།", + "model.message_model.explore_other_text": "གཞན་དག་གྲུབ་མཐའ་ཚུ་ཞིབ་ཕྱིར་བྱས།", + "model.message_model.message_type": "ཐོབ་མེད་པའི་དྲན་བསྡུར།", + "model.message_model.message": "ཡིག་ཆ་འདི་མིན་འོང་བ་ཡང་ན་ཁྱེད་ཀྱིས་འཚོལ་ནིའི་དབང་སྤྲོད་མེད།", "modal.sign_up.default.sign_up__to_get_more_from_sefaria":"དཔེ་ཆ་དྲ་ཐོག་དཔེ་མཛོད་ནས་འདི་ལས་མང་བ་ཐོབ་པ་ལ་ཞུགས་ཐོ་གསར་འགོད་བྱོས།", "model.sign_up.add_connection.connection_to_another_text": "", "model.sign_up.add_connection.create_free_account": "", @@ -256,6 +261,8 @@ "header.profileMenu.profile": "ངོ་སྤྲོད་སྙིང་བསྡུས།", "header.profileMenu.create_New_Sheet": "ཤོག་ངོས་གསར་པ་ཞིག་བཟོས།", "header.profileMenu.account_settings": "ཁ་བྱང་སྒྲིག་འགོད།", + "profile.buddhist_tracker.favorite_texts": "ཁྱེད་རང་དགའ་ཤོས་ཀྱི་ཡིག་ཆ།", + "profile.buddhist_tracker.favorite_sheets": "ཁྱེད་རང་དགའ་ཤོས་ཀྱི་ཤོག་ལེབ།", "header.profileMenu.log_out": "ཕྱིར་ཐོན།", "profile.tab.collection.description": "ཟིན་བྲིས་བེད་སྤྱོད་བཏང་ནས། ལུང་འདྲེན་ཕྱོགས་བསྡུ་དང་། དེའི་སྐོར་ལ་རྩོམ་འབྲི་བ། སློབ་ཚན་སྒྲིག་པ། དཔྱད་རྩོམ་འབྲི་བ་ལ་སོགས་པ་གནང་ཐུབ་པ་ཡིན།", "profile.buddhish_text_tracker":"སྤྱི་བསྡོམས་རྩིས་ཁྲ།", @@ -349,6 +356,7 @@ "text.don’t_lose_that_thought": "ཁྱེད་ཀྱི་གོ་རྟོགས་གསར་པ་དེ་མ་བརླགས་པར་བྱེད་དགོས།", "text.create_free_account_to_do_more_on_Pecha": "དཔེ་ཆ་དྲ་བའི་ཆ་རྐྱེན་ཚང་མ་སྤྱོད་པ་ལ། རིན་མེད་ཐོག་ནས་ཐོ་འགོད་གྱིས།", "text.take_notes_on_this_text": "ཡིག་ཆ་འདི་ལ་མཆན་རྒྱོབས།", + "text.root_text": "རྩ་བ།", "text.get_updates_on_new_features": "ཁྱད་ཆོས་ཁ་སྣོན་བྱས་པ་སོགས་ལ་ལྟོས།", "text.citing":"ལུང་འདྲེན།", "text.sites_that_are_listed_here_use_the":"བཀོལ་སྤྱོད་བྱེད་པ་ལ་ཐོ་རུ་བཀོད་པའི་ས་ཆ་རྣམས།", @@ -467,6 +475,7 @@ "sheet.sheet_layout": "ཟིན་བྲིས་ཀྱི་རྣམ་པ།", "sheet.stacked": "གོང་འོག།", "sheet.side_by_side": "གཡས་གཡོན།", + "sheet.editor.source.input_field.placeholder": "ཡི་གེ ཡང་ན བསམ་བཀོད་མཛོད་པའི་མིང་འཚོལ།", "sheet.side_by_side_layout": "གཡས་གཡོན་སྒྲིག་འགོད།", "sheet.source_sheet_copied": "ཟིན་བྲིས་ངོ་བཤུ་བྱས་ཟིན།", "sheet.view_copy": "ངོ་བཤུས་ལ་ལྟ།", diff --git a/static/js/sefaria/sefaria.js b/static/js/sefaria/sefaria.js index a00d0579af..7fe06324c9 100644 --- a/static/js/sefaria/sefaria.js +++ b/static/js/sefaria/sefaria.js @@ -55,7 +55,7 @@ Sefaria = extend(Sefaria, { let book, index, nums; for (let i = first.length; i >= 0; i--) { - book = first.slice(0, i); + book = first.slice(0, i); if (Sefaria.virtualBooks.includes(book)) { // todo: This assumes that this is a depth one integer indexed node const numberMatch = first.match(/([\d ]+)$/); @@ -452,6 +452,17 @@ Sefaria = extend(Sefaria, { } return null; }, + isTibetan: (text) => { + // Tibetan Unicode range: U+0F00 to U+0FFF + const tibetanPattern = /^[\u0F00-\u0FFF]+$/; + return tibetanPattern.test(text); + }, + + isEnglish: (text) => { + // English uses basic Latin alphabet (A-Z, a-z) and spaces + const englishPattern = /^[A-Za-z\s]+$/; + return englishPattern.test(text); + }, getText: function(ref, settings) { // returns a promise settings = this._complete_text_settings(settings); @@ -1087,6 +1098,7 @@ Sefaria = extend(Sefaria, { // - Index Data // - Search TOC order for (let i = 0; i < tocBranch.length; i++) { + let thisOrder = parentsOrders.concat([i]) ; let thisPath = (parentsPath ? parentsPath + "/" : "") + ("category" in tocBranch[i] ? tocBranch[i].category : tocBranch[i].title); @@ -1207,6 +1219,9 @@ Sefaria = extend(Sefaria, { // If no open calls found, call the texts API. // Called with context:1 because this is our most common mode, maximize change of saving an API Call return Sefaria.getText(ref, {context: 1}); + }, + getEnRefForBO: (boRef) => { + }, ref: function(ref, callback) { if (callback) { diff --git a/static/js/sefaria/util.js b/static/js/sefaria/util.js index acf8e7c25a..d9a6ab63c3 100644 --- a/static/js/sefaria/util.js +++ b/static/js/sefaria/util.js @@ -98,7 +98,7 @@ class Util { const yearString = getTibetanNumberAsString(year); const monthString = getTibetanNumberAsString(month); const dateString = getTibetanNumberAsString(date); - return `སྤྱི་ལོ ${yearString} ཟླ་ ${monthString} ཚེས་ ${dateString}` + return `སྤྱི་ལོ ${yearString} ཟླ་ ${monthString} ཚེས་ ${dateString}` } function getTibetanNumberAsString(num) { diff --git a/templates/base.html b/templates/base.html index a88e3c6757..142ef58030 100644 --- a/templates/base.html +++ b/templates/base.html @@ -2,6 +2,7 @@ {% load static %} {% load sefaria_tags %} {% get_static_prefix as STATIC_PREFIX %} +{% load cache_busting %} @@ -116,15 +117,15 @@ - - - - - + + + + + {% block static_css %} {% if not html %} - + {% endif %} {% endblock %} diff --git a/templates/js/sefaria.js b/templates/js/sefaria.js index 82c8f486a9..4fb9b680c0 100644 --- a/templates/js/sefaria.js +++ b/templates/js/sefaria.js @@ -2,4 +2,4 @@ {{ data_js }} {{ sefaria_js }} {% endautoescape %} -Sefaria.apiHost = "https://www.sefaria.org"; \ No newline at end of file +Sefaria.apiHost = "https://www.pecha.org"; \ No newline at end of file diff --git a/templates/registration/password_reset_complete.html b/templates/registration/password_reset_complete.html index dcd8d22bff..e74bf582b3 100644 --- a/templates/registration/password_reset_complete.html +++ b/templates/registration/password_reset_complete.html @@ -1,16 +1,27 @@ {% extends "base.html" %} {% load i18n %} +{% load static %} {% block title %}{% trans "registration.password_reset_complete.password_reset" %} | {% trans "common.name_pecha" %}{% endblock %} - +{% block head %} + +{% endblock %} {% block content %}

- {% trans "registration.password_reset_complete.your_new_password_has_been_saved" %} + {% trans "registration.password_reset_complete.your_new_password_has_been_saved" %}

- {% trans "registration.password_reset_complete.back_to_login" %} + {% trans "registration.password_reset_complete.back_to_login" %}
diff --git a/templates/registration/password_reset_done.html b/templates/registration/password_reset_done.html index fecc0fcbe2..fd8f66080f 100644 --- a/templates/registration/password_reset_done.html +++ b/templates/registration/password_reset_done.html @@ -1,13 +1,22 @@ {% extends "base.html" %} {% load i18n %} +{% load static %} {% block title %}{% trans "registration.password_reset_done.password_reset_link_sent" %} | {% trans "common.name_pecha" %}{% endblock %} - + {% block content %}

- {% trans "registration.password_reset_done.messages" %} + {% trans "registration.password_reset_done.messages" %}