diff --git a/sefaria/sheetsUtils.js b/sefaria/sheetsUtils.js
new file mode 100644
index 0000000000..77a2579853
--- /dev/null
+++ b/sefaria/sheetsUtils.js
@@ -0,0 +1,69 @@
+import Sefaria from "../static/js/sefaria/sefaria";
+
+export async function getSegmentObjs(refs) {
+ /*
+ Given an array of ref-strings (could also be ranged refs),
+ turn each ref to segment object and return an array of all segments
+ */
+ const segments = [];
+
+ for (const ref of refs) {
+ const text = await Sefaria.getText(ref, { stripItags: 1 });
+ const newSegments = Sefaria.makeSegments(text, false);
+ segments.push(...newSegments);
+ }
+ return segments;
+}
+export async function getNormalRef(ref) {
+ /*
+ Given a ref-string, get he and en normal ref-string
+ */
+ const refObj = await Sefaria.getRef(ref);
+ return {en: refObj.ref, he: refObj.heRef}
+}
+function placedSegmentMapper(lang, segmented, includeNumbers, s) {
+ /*
+ Map each segment object to a formatted text string
+ */
+ if (!s[lang]) {return ""}
+
+ let numStr = "";
+ if (includeNumbers) {
+ const num = (lang=="he") ? Sefaria.hebrew.encodeHebrewNumeral(s.number) : s.number;
+ numStr = "(" + num + ") ";
+ }
+ let str = "" + numStr + s[lang] + " ";
+ if (segmented) {
+ str = "
" + str + "
";
+ }
+ str = str.replace(/(
)+/g, ' ')
+ return str;
+}
+export const segmentsToSourceText = (segments, lan) => {
+ /*
+ Turn array of segment objects into one chunk of formatted text
+ */
+ const segmented = shouldBeSegmented(segments[0].ref);
+ const includeNumbers = shouldIncludeSegmentNums(segments[0].ref);
+ return(segments.map(placedSegmentMapper.bind(this, lan, segmented, includeNumbers))
+ .filter(Boolean)
+ .join(""));
+}
+function shouldIncludeSegmentNums(ref){
+ /*
+ Decide if segment of this ref should have segment numbers when turned into text chunk
+ */
+ const indexTitle = Sefaria.refIndexTitle(ref);
+ const categories = Sefaria.refCategories(ref);
+ if (categories.includes("Talmud")) {return false}
+ if (indexTitle === "Pesach Haggadah") {return false}
+ if (categories === 1) {return false}
+ return true;
+}
+function shouldBeSegmented(ref){
+ /*
+ Decide if segment of this ref should be followed by new line when turned into text chunk
+ */
+ const categories = Sefaria.refCategories(ref);
+ return !(categories[0] in {"Tanakh": 1, "Talmud": 1});
+}
\ No newline at end of file
diff --git a/static/js/AddToSourceSheet.jsx b/static/js/AddToSourceSheet.jsx
index 8efaccc60c..91cca73fd9 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..1f0b3ff2aa 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,
@@ -1866,25 +1868,7 @@ const insertMedia = (editor, mediaUrl) => {
Transforms.move(editor);
}
-
-function placed_segment_mapper(lang, segmented, includeNumbers, s) {
- if (!s[lang]) {return ""}
-
- let numStr = "";
- if (includeNumbers) {
- const num = (lang=="he") ? Sefaria.hebrew.encodeHebrewNumeral(s.number) : s.number;
- numStr = "(" + num + ") ";
- }
- let str = "" + numStr + s[lang] + " ";
- if (segmented) {
- str = "" + str + "
";
- }
- str = str.replace(/(
)+/g, ' ')
- return str;
-}
-
-
-const insertSource = (editor, ref) => {
+const insertSource = async (editor, ref) => {
const path = editor.selection.anchor.path;
Transforms.setNodes(editor, { loading: true }, {at: path});
@@ -1892,47 +1876,39 @@ const insertSource = (editor, ref) => {
const nodeAbove = getNodeAbove(path, editor)
const nodeBelow = getNodeBelow(path, editor)
- Sefaria.getText(ref, {stripItags: 1}).then(text => {
- let segments = Sefaria.makeSegments(text, false);
- segments = Sefaria.stripImagesFromSegments(segments);
-
- let includeNumbers = $.inArray("Talmud", text.categories) == -1;
- includeNumbers = text.indexTitle === "Pesach Haggadah" ? false : includeNumbers;
- const segmented = !(text.categories[0] in {"Tanakh": 1, "Talmud": 1});
-
- const enText = segments.map(placed_segment_mapper.bind(this, "en", segmented, includeNumbers))
- .filter(Boolean)
- .join("");
- const heText = segments.map(placed_segment_mapper.bind(this, "he", segmented, includeNumbers))
- .filter(Boolean)
- .join("");
-
- let fragment = [{
- type: "SheetSource",
- node: editor.children[0].nextNode,
- ref: text.ref,
- heRef: text.heRef,
- heText: parseSheetItemHTML(heText),
- enText: parseSheetItemHTML(enText),
- title: null,
- children: [
- {text: ""},
- ]
- }];
-
- if (!(nodeBelow.node && (nodeBelow.node.type == "SheetOutsideText" || nodeBelow.node.type == "paragraph" ) )) {
- fragment.push({type: 'spacer', children: [{text: ""}]})
- }
- Transforms.setNodes(editor, { loading: false }, { at: path });
- addItemToSheet(editor, fragment);
- checkAndFixDuplicateSheetNodeNumbers(editor)
- if (nodeAbove.node && (nodeAbove.node.type == "SheetOutsideText" || nodeAbove.node.type == "paragraph" ) ) {
- Transforms.delete(editor, {at: path})
- }
-
-
- Transforms.move(editor, { unit: 'block', distance: 1 })
- });
+ const {en: normalEnRef, he: normalHeRef} = await sheetsUtils.getNormalRef(ref);
+
+ let segments = await sheetsUtils.getSegmentObjs([ref])
+
+ const enText = sheetsUtils.segmentsToSourceText(segments, 'en');
+
+ const heText = sheetsUtils.segmentsToSourceText(segments, 'he');
+
+ let fragment = [{
+ type: "SheetSource",
+ node: editor.children[0].nextNode,
+ ref: normalEnRef,
+ heRef: normalHeRef,
+ heText: parseSheetItemHTML(heText),
+ enText: parseSheetItemHTML(enText),
+ title: null,
+ children: [
+ {text: ""},
+ ]
+ }];
+
+ if (!(nodeBelow.node && (nodeBelow.node.type == "SheetOutsideText" || nodeBelow.node.type == "paragraph" ) )) {
+ fragment.push({type: 'spacer', children: [{text: ""}]})
+ }
+ Transforms.setNodes(editor, { loading: false }, { at: path });
+ addItemToSheet(editor, fragment);
+ checkAndFixDuplicateSheetNodeNumbers(editor)
+ if (nodeAbove.node && (nodeAbove.node.type == "SheetOutsideText" || nodeAbove.node.type == "paragraph" ) ) {
+ Transforms.delete(editor, {at: path})
+ }
+
+
+ Transforms.move(editor, { unit: 'block', distance: 1 })
};
diff --git a/static/js/sefaria/sefaria.js b/static/js/sefaria/sefaria.js
index 593dc7d023..e3e71c024d 100644
--- a/static/js/sefaria/sefaria.js
+++ b/static/js/sefaria/sefaria.js
@@ -274,6 +274,12 @@ Sefaria = extend(Sefaria, {
let index = Sefaria.index(pRef.index);
return index && index.categories ? index.categories : [];
},
+ refIndexTitle: function(ref) {
+ let pRef = Sefaria.parseRef(ref);
+ if ("error" in pRef) { return null; }
+ let index = Sefaria.index(pRef.index);
+ return index?.title
+ },
sectionRef: function(ref, deriveIfNotFound=false) {
// Returns the section level ref for `ref` or null if no data is available
const oref = this.getRefFromCache(ref);