diff --git a/build/ci/integration-values.yaml b/build/ci/integration-values.yaml
index 007a28c360..1127d6e5cc 100644
--- a/build/ci/integration-values.yaml
+++ b/build/ci/integration-values.yaml
@@ -57,7 +57,6 @@ localSettings:
DEBUG: true
DOMAIN_LANGUAGE: {}
APSCHEDULER_NAME: "apscheduler-{{ .Values.deployEnv }}"
- TURN_SERVER: ''
USE_CLOUDFLARE: false
FRONT_END_URL: "http://${NAME}.integration.sefaria.org"
OFFLINE: false
diff --git a/build/ci/production-values.yaml b/build/ci/production-values.yaml
index 138910a0f7..aaf6ed61c7 100644
--- a/build/ci/production-values.yaml
+++ b/build/ci/production-values.yaml
@@ -235,7 +235,6 @@ localSettings:
}
MONGO_HOST: "mongo"
APSCHEDULER_NAME: "apscheduler-{{ .Values.deployEnv }}"
- TURN_SERVER: ''
USE_CLOUDFLARE: false
FRONT_END_URL: "http://www.sefaria.org"
OFFLINE: false
diff --git a/build/ci/sandbox-values.yaml b/build/ci/sandbox-values.yaml
index f6984b77f0..21dc2b826b 100644
--- a/build/ci/sandbox-values.yaml
+++ b/build/ci/sandbox-values.yaml
@@ -53,7 +53,6 @@ localSettings:
DEBUG: false
DOMAIN_LANGUAGE: {}
APSCHEDULER_NAME: "apscheduler-{{ .Values.deployEnv }}"
- TURN_SERVER: ''
USE_CLOUDFLARE: false
FRONT_END_URL: "http://${NAME}.cauldron.sefaria.org"
OFFLINE: false
diff --git a/build/notify/notifyEnd.js b/build/notify/notifyEnd.js
index 88f4d8d6f3..1edc5d3a29 100644
--- a/build/notify/notifyEnd.js
+++ b/build/notify/notifyEnd.js
@@ -21,7 +21,7 @@ console.log(`
const jobKeys = [
"Jest",
- "PyTest",
+ "Continuous Testing: PyTest",
"Playwright",
];
diff --git a/helm-chart/sefaria-project/templates/configmap/local-settings-file.yaml b/helm-chart/sefaria-project/templates/configmap/local-settings-file.yaml
index d59daf235f..ee9d51ff01 100644
--- a/helm-chart/sefaria-project/templates/configmap/local-settings-file.yaml
+++ b/helm-chart/sefaria-project/templates/configmap/local-settings-file.yaml
@@ -144,10 +144,6 @@ data:
SEARCH_INDEX_NAME_TEXT = 'text' # name of the ElasticSearch index to use
SEARCH_INDEX_NAME_SHEET = 'sheet'
- TURN_SERVER = os.getenv("TURN_SERVER") #coturn.cauldron.sefaria.org
- TURN_SECRET= os.getenv("TURN_SECRET")
- TURN_USER = os.getenv("TURN_USER")
-
USE_NODE = True
NODE_HOST = "http://{}:3000".format(nodejsHost)
NODE_TIMEOUT = 5
diff --git a/helm-chart/sefaria-project/templates/configmap/local-settings.yaml b/helm-chart/sefaria-project/templates/configmap/local-settings.yaml
index efab2f1294..7c3cf997b0 100644
--- a/helm-chart/sefaria-project/templates/configmap/local-settings.yaml
+++ b/helm-chart/sefaria-project/templates/configmap/local-settings.yaml
@@ -9,7 +9,6 @@ data:
DEBUG: "{{ .Values.localSettings.DEBUG }}"
DOMAIN_LANGUAGE: {{ .Values.localSettings.DOMAIN_LANGUAGE | toJson | quote }}
APSCHEDULER_NAME: {{ tpl .Values.localSettings.APSCHEDULER_NAME . | quote }}
- TURN_SERVER: {{ .Values.localSettings.TURN_SERVER | quote }}
USE_CLOUDFLARE: "{{ .Values.localSettings.USE_CLOUDFLARE }}"
FRONT_END_URL: {{ .Values.localSettings.FRONT_END_URL | quote }}
OFFLINE: "{{ .Values.localSettings.OFFLINE }}"
diff --git a/helm-chart/sefaria-project/values.yaml b/helm-chart/sefaria-project/values.yaml
index 11fd51195f..040e37a7ee 100644
--- a/helm-chart/sefaria-project/values.yaml
+++ b/helm-chart/sefaria-project/values.yaml
@@ -357,8 +357,6 @@ secrets:
# SEFARIA_DB_USER:
# SEFARIA_DB_PASSWORD:
# SEARCH_URL
- # TURN_SECRET:
- # TURN_USER:
# SEFARIA_BOT_API_KEY:
# CLOUDFLARE_ZONE:
# CLOUDFLARE_EMAIL:
@@ -451,7 +449,6 @@ localSettings:
# https://www.sefaria.org: english
# https://www.sefaria.org.il: hebrew
APSCHEDULER_NAME: "apscheduler-{{ .Values.deployEnv }}"
- TURN_SERVER: ''
USE_CLOUDFLARE: false
FRONT_END_URL: "http://www.sefaria.org" # Use "http://${ENV_NAME}.cauldron.sefaria.org" in cauldrons
OFFLINE: "False"
diff --git a/sefaria/model/schema.py b/sefaria/model/schema.py
index 77cb1f4f07..ca075ab46c 100644
--- a/sefaria/model/schema.py
+++ b/sefaria/model/schema.py
@@ -2646,7 +2646,7 @@ class AddressPerek(AddressInteger):
}
section_patterns = {
"en": r"""(?:(?:[Cc]h(apters?|\.)|[Pp]erek|s\.)?\s*)""", # the internal ? is a hack to allow a non match, even if 'strict'
- "he": fr"""(?:\u05d1?{AddressType.reish_samekh_reg}\u05e4((?:"|\u05f4|''|'\s)|(?=[\u05d0-\u05ea]+(?:"|\u05f4|''|'\s))) # Peh (for 'perek') maybe followed by a quote of some sort OR lookahead for some letters followed by a quote (e.g. פי״א for chapter 11)
+ "he": fr"""(?:\u05d1?{AddressType.reish_samekh_reg}\u05e4((?:"|\u05f4|''|'\s|\s)|(?=[\u05d0-\u05ea]+(?:"|\u05f4|''|'\s))) # Peh (for 'perek') maybe followed by a quote of some sort OR lookahead for some letters followed by a quote (e.g. פי״א for chapter 11)
|\u05e4\u05bc?\u05b6?\u05e8\u05b6?\u05e7(?:\u05d9\u05b4?\u05dd)?\s* # or 'perek(ym)' spelled out, followed by space
)"""
}
diff --git a/static/css/s2.css b/static/css/s2.css
index 27ebaf7302..29b4f4ea93 100644
--- a/static/css/s2.css
+++ b/static/css/s2.css
@@ -10283,17 +10283,21 @@ span.purim-emoji img{
.interface-english .spacer:only-of-type.empty:before,
.interface-english .spacer:only-of-type.empty:before {
content: "Write something... ";
+ margin-inline-start: 50px;
}
.interface-hebrew .sheetItem:only-of-type.empty .SheetOutsideText:before,
.interface-hebrew .spacer:only-of-type.empty:before,
.interface-hebrew .spacer:only-of-type.empty:before {
content: "לכתוב משהו...";
+ margin-inline-start: 50px;
}
.editorAddInterface {
position: relative;
pointer-events:none;
background-color: transparent;
+ margin-inline-start: 50px;
}
+
.editorAddInterface:before {
content: "";
margin-inline-start:-46px;
@@ -10313,6 +10317,34 @@ span.purim-emoji img{
box-sizing: border-box;
box-shadow: 0px 1px 3px 0px #00000040;
}
+.editorAddLineButton {
+ position: relative;
+ background-color: transparent;
+ margin-inline-start: 50px;
+}
+.hidden.editorAddLineButton::before {
+ display: none;
+}
+
+.editorAddLineButton:before {
+ content: "";
+ margin-inline-start:-46px;
+ position: absolute;
+ width: 30px;
+ height: 30px;
+ transform: rotate(45deg);
+ background-color: white;
+ background-image: url("/static/icons/heavy-x-dark.svg");
+ border: 1px solid var(--light-grey);
+ background-size: 14px;
+ border-radius: 50%;
+ /*pointer-events:auto;*/
+ cursor: pointer;
+ background-repeat: no-repeat;
+ background-position: center;
+ box-sizing: border-box;
+ box-shadow: 0px 1px 3px 0px #00000040;
+}
.editorAddInterface:hover::before {
background-color: var(--lighter-grey);
@@ -10336,6 +10368,10 @@ background-color: white;
pointer-events: none;
display: inline-block;
}
+.hidden.editorAddInterface::before {
+ display: none;
+}
+
.addInterfaceInput .textPreview {
border-inline-start: 4px solid;
diff --git a/static/js/AddToSourceSheet.jsx b/static/js/AddToSourceSheet.jsx
index 8efaccc60c..3b456e1a11 100644
--- a/static/js/AddToSourceSheet.jsx
+++ b/static/js/AddToSourceSheet.jsx
@@ -12,6 +12,7 @@ import Component from 'react-class';
import sanitizeHtml from 'sanitize-html';
import { SignUpModalKind } from './sefaria/signupModalContent';
import { GDocAdvertBox } from './Promotions';
+import * as sheetsUtils from './sefaria/sheetsUtils'
@@ -89,7 +90,34 @@ class AddToSourceSheetBox extends Component {
}, this.confirmAdd);
}
}
- addToSourceSheet() {
+
+ //return the initial index of the suffix of string1 which also constitutes a prefix for string2
+ longestSuffixPrefixIndex(string1, string2) {
+ let longestSuffixIndex = 0;
+ for (let i = 0; i < string1.length; i++){
+ let suffix = string1.slice(i);
+ if (string2.startsWith(suffix)) {
+ longestSuffixIndex = i;
+ }
+ }
+ return longestSuffixIndex;
+ }
+ //return the final index of the prefix of string1 which also constitutes a suffix for string2
+ longestPrefixSuffixIndex(string1, string2) {
+ let longestPrefixIndex = 0;
+ for (let i = 0; i < string1.length; i++) {
+ let prefix = string1.slice(0, i + 1);
+ if (string2.endsWith(prefix)) {
+ longestPrefixIndex = i + 1;
+ }
+ }
+ return longestPrefixIndex;
+ }
+
+ normalize(text){
+ return(text.replaceAll(/(
)+/g, ' ').replace(/\u2009/g, ' ').replace(/<[^>]*>/g, ''));
+ }
+ async addToSourceSheet() {
if (!Sefaria._uid) {
this.props.toggleSignUpModal(SignUpModalKind.AddToSheet);
}
@@ -111,15 +139,40 @@ class AddToSourceSheetBox extends Component {
}
} else if (this.props.srefs) { //regular use - this is currently the case when the component is loaded in the sidepanel or in the modal component via profiles and notes pages
source.refs = this.props.srefs;
+
+
+
const { en, he } = this.props.currVersions ? this.props.currVersions : {"en": null, "he": null}; //the text we are adding may be non-default version
if (he) { source["version-he"] = he; }
if (en) { source["version-en"] = en; }
// If something is highlighted and main panel language is not bilingual:
// Use passed in language to determine which version this highlight covers.
- var selectedWords = this.props.selectedWords; //if there was highlighted single panel
+ let selectedWords = this.props.selectedWords; //if there was highlighted single panel
if (selectedWords && language != "bilingual") {
- source[language.slice(0,2)] = selectedWords;
+ let lan = language.slice(0,2);
+ let segments = await sheetsUtils.getSegmentObjs(source.refs);
+ selectedWords = this.normalize(selectedWords);
+ segments = segments.map(segment => ({
+ ...segment,
+ [lan]: this.normalize(segment[lan])
+ }));
+ for (let iSegment = 0; iSegment < segments.length; iSegment++) {
+ const segment = segments[iSegment];
+ if (iSegment == 0){
+ let criticalIndex = this.longestSuffixPrefixIndex(segment[lan], selectedWords);
+ const ellipse = criticalIndex == 0 ? "" : "...";
+ segment[lan] = ellipse + segment[lan].slice(criticalIndex);
+ }
+ else if (iSegment == segments.length-1){
+ let criticalIndex = this.longestPrefixSuffixIndex(segment[lan], selectedWords);
+ const ellipse = criticalIndex == segment[lan].length-1 ? "" : "...";
+ const chunk = segment[lan].slice(0, criticalIndex)
+ segment[lan] = chunk + ellipse;
+ }
+ }
+
+ source[lan] = sheetsUtils.segmentsToSourceText(segments, lan);
}
}
if (this.checkContentForImages(source.refs)) {
diff --git a/static/js/Editor.jsx b/static/js/Editor.jsx
index cd7a7ec624..027d5c5ec8 100644
--- a/static/js/Editor.jsx
+++ b/static/js/Editor.jsx
@@ -5,6 +5,8 @@ import {Editor, createEditor, Range, Node, Transforms, Path, Text, Point, Elemen
import {Slate, Editable, ReactEditor, withReact, useSlate, useSelected, useFocused} from 'slate-react'
import isHotkey from 'is-hotkey'
import Sefaria from './sefaria/sefaria';
+import * as sheetsUtils from './sefaria/sheetsUtils'
+
import {
SheetMetaDataBox,
@@ -147,6 +149,59 @@ const format_to_html_lookup = format_tag_pairs.reduce((obj, item) => {
return {node: bottom, path: bottomPath}
};
+const isMultiNodeSelection = (editor) => {
+ if (!editor.selection) {return false}
+
+ const [start, end] = Range.edges(editor.selection);
+
+ const startPath = start.path;
+ const endPath = end.path;
+
+ // If the start and end paths are different, it means multiple nodes are selected
+ return !Path.equals(startPath, endPath);
+};
+const moveAnchorToEndOfClosestParagraph = (editor) => {
+ const { selection } = editor;
+
+ if (selection && Range.isCollapsed(selection)) {
+ const { anchor } = selection;
+ const [closestParagraphNode, closestParagraphPath] = Editor.above(editor, {
+ at: anchor.path,
+ match: (n) => n.type === 'paragraph',
+ }) || [];
+
+ if (closestParagraphNode) {
+ const endPoint = Editor.end(editor, closestParagraphPath);
+
+ Transforms.select(editor, {
+ anchor: endPoint,
+ focus: endPoint,
+ });
+ }
+ }
+};
+const moveAnchorToEndOfCurrentNode = (editor) => {
+ const { selection } = editor;
+
+ if (selection && Range.isCollapsed(selection)) {
+ const { anchor } = selection;
+ const node = Editor.node(editor, anchor);
+
+ if (node) {
+ const [, path] = node;
+ const endPoint = Editor.end(editor, path);
+
+ Transforms.select(editor, {
+ anchor: endPoint,
+ focus: endPoint
+ });
+ }
+ }
+};
+const insertNewLine = (editor) => {
+ moveAnchorToEndOfClosestParagraph(editor);
+ editor.insertBreak();
+}
export const deserialize = el => {
if (el.nodeType === 3) {
@@ -1062,6 +1117,9 @@ const AddInterface = ({ attributes, children, element }) => {
const Element = (props) => {
const { attributes, children, element } = props;
+ const editor = useSlate();
+
+
const sheetItemClasses = {
sheetItem: 1,
empty: !(Node.string(element)),
@@ -1180,11 +1238,32 @@ const Element = (props) => {
" + str + "
"; - } - str = str.replace(/(" + str + "
"; + } + str = str.replace(/(