Skip to content

Commit

Permalink
feat(Topic Editor): can add pictures with captions
Browse files Browse the repository at this point in the history
  • Loading branch information
stevekaplan123 committed Nov 22, 2023
1 parent 80f1a8c commit 1bf6cf8
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 51 deletions.
25 changes: 8 additions & 17 deletions reader/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3104,7 +3104,6 @@ def add_new_topic_api(request):
isTopLevelDisplay = data["category"] == Topic.ROOT
t = Topic({'slug': "", "isTopLevelDisplay": isTopLevelDisplay, "data_source": "sefaria", "numSources": 0})
update_topic_titles(t, data)

if not isTopLevelDisplay: # not Top Level so create an IntraTopicLink to category
new_link = IntraTopicLink({"toTopic": data["category"], "fromTopic": t.slug, "linkType": "displays-under", "dataSource": "sefaria"})
new_link.save()
Expand All @@ -3116,8 +3115,11 @@ def add_new_topic_api(request):
t.data_source = "sefaria" # any topic edited manually should display automatically in the TOC and this flag ensures this
if "description" in data:
t.change_description(data["description"], data.get("categoryDescription", None))
t.save()

if "image" in data:
t.image = data["image"]

t.save()
library.build_topic_auto_completer()
library.get_topic_toc(rebuild=True)
library.get_topic_toc_json(rebuild=True)
Expand Down Expand Up @@ -3570,28 +3572,17 @@ def profile_follow_api(request, ftype, slug):
@catch_error_as_json
def topic_upload_photo(request):
if not request.user.is_authenticated:
return jsonResponse({"error": _("You must be logged in to update your profile photo.")})
return jsonResponse({"error": _("You must be logged in to update a topic photo.")})
if request.method == "POST":
from io import BytesIO
import uuid
import base64
"""
"image" : {
"image_uri" : "https://storage.googleapis.com/img.sefaria.org/topics/shabbat.jpg",
"image_caption" : {
"en" : "Friday Evening, Isidor Kaufmann, Austria c. 1920. The Jewish Museum, Gift of Mr. and Mrs. M. R. Schweitzer",
"he" : "שישי בערב, איזידור קאופמן, וינה 1920. המוזיאון היהודי בניו יורק, מתנת מר וגברת מ.ר. שוויצר"
}
}
Validation that the image_uri url should start with https://storage.googleapis.com/img.sefaria.org/topics/
"""
bucket_name = GoogleStorageManager.TOPICS_BUCKET
img_file_in_mem = BytesIO(base64.b64decode(request.POST.get('file')))
old_filename = request.POST.get('old_filename')
img_url = GoogleStorageManager.upload_file(img_file_in_mem, f"topics/{request.user.id}-{uuid.uuid1()}.gif",
bucket_name)
#big_pic_url = GoogleStorageManager.upload_file(get_resized_file(image, (250, 250)), "{}-{}.png".format(profile.slug, now), bucket_name, old_big_pic_filename)

add_image_to_topic(topic_slug, img_url, en_caption, he_caption)
bucket_name, old_filename=old_filename)
#img_url = 'https://storage.googleapis.com/img.sefaria.org/topics/41861-683e06f6-891a-11ee-be47-4a26184f1ad1.gif'
return jsonResponse({"url": img_url})
return jsonResponse({"error": "Unsupported HTTP method."})

Expand Down
3 changes: 3 additions & 0 deletions sefaria/helper/topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,9 @@ def update_topic(topic_obj, **kwargs):
if "description" in kwargs or "categoryDescription" in kwargs:
topic_obj.change_description(kwargs.get("description", None), kwargs.get("categoryDescription", None))

if "image" in kwargs:
topic_obj.image = kwargs["image"]

topic_obj.save()

if kwargs.get('rebuild_topic_toc', True):
Expand Down
13 changes: 4 additions & 9 deletions static/js/AdminEditor.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, {useRef, useState} from "react";
import Sefaria from "./sefaria/sefaria";
import {AdminToolHeader, InterfaceText, PictureUploader, TitleVariants} from "./Misc";
import {AdminToolHeader, InterfaceText, TitleVariants} from "./Misc";
import sanitizeHtml from 'sanitize-html';
import classNames from "classnames";
const options_for_form = {
"Picture": {label: "Picture", field: "picture", placeholder: "Add a picture.", type: "picture"},
// "Picture": {label: "Picture", field: "picture", placeholder: "Add a picture.", type: "picture"},
"English Caption": {label: "English Caption", field: "enImgCaption", placeholder: "Add a caption for topic picture"},
"Hebrew Caption": {label: "Hebrew Caption", field: "heImgCaption", placeholder: "Add a Hebrew caption for topic picture"},
"Title": {label: "Title", field: "enTitle", placeholder: "Add a title."},
"Hebrew Title": {label: "Hebrew Title", field: "heTitle", placeholder: "Add a title."},
"English Description": {
Expand Down Expand Up @@ -126,10 +128,6 @@ const AdminEditor = ({title, data, close, catMenu, updateData, savingStatus,
}
updateData({...data});
}
const handlePictureChange = (url) => {
data["picture"] = url;
updateData({...data});
}
const handleTitleVariants = (newTitles, field) => {
const newData = {...data};
newData[field] = newTitles.map(x => Object.assign({}, x));
Expand Down Expand Up @@ -166,9 +164,6 @@ const AdminEditor = ({title, data, close, catMenu, updateData, savingStatus,
const item = ({label, field, placeholder, type, dropdown_data}) => {
let obj;
switch(type) {
case 'picture':
obj = <PictureUploader callback={handlePictureChange}/>;
break;
case 'dropdown':
obj = getDropdown(field, dropdown_data, placeholder);
break;
Expand Down
37 changes: 17 additions & 20 deletions static/js/Misc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {ContentText} from "./ContentText";
import ReactTags from "react-tag-autocomplete";
import {AdminEditorButton, useEditToggle} from "./AdminEditor";
import {CategoryEditor, ReorderEditor} from "./CategoryEditor";
import {refSort} from "./TopicPage";
import {refSort, TopicImage} from "./TopicPage";
import {TopicEditor} from "./TopicEditor";
import {generateContentForModal, SignUpModalKind} from './sefaria/signupModalContent';
import {SourceEditor} from "./SourceEditor";
Expand Down Expand Up @@ -1235,7 +1235,8 @@ const EditorForExistingTopic = ({ toggle, data }) => {
origBirthYear: data?.properties?.birthYear?.value,
origDeathPlace: data?.properties?.deathPlace?.value,
origDeathYear: data?.properties?.deathYear?.value,
origEra: data?.properties?.era?.value
origEra: data?.properties?.era?.value,
origImage: data?.image,

};

Expand Down Expand Up @@ -1578,14 +1579,18 @@ FollowButton.propTypes = {

};

const PictureUploader = (callback) => {
const PictureUploader = ({callback, old_filename, caption}) => {
/*
`old_filename` is passed to API so that if it exists, it is deleted
*/
const fileInput = useRef(null);

var uploadImage = function(imageData) {
const formData = new FormData();
formData.append('file', imageData.replace(/data:image\/(jpe?g|png|gif);base64,/, ""));
// formData.append('file', imageData);

if (old_filename !== "") {
formData.append('old_filename', old_filename);
}
$.ajax({
url: Sefaria.apiHost + "/api/topics/upload-image",
type: 'POST',
Expand All @@ -1594,9 +1599,6 @@ const PictureUploader = (callback) => {
processData: false,
success: function(data) {
callback(data.url);
// $("#inlineAddMediaInput").val(data.url);
// $("#addmediaDiv").find(".button").first().trigger("click");
// $("#inlineAddMediaInput").val("");
},
error: function(e) {
console.log("photo upload ERROR", e);
Expand All @@ -1623,18 +1625,13 @@ const PictureUploader = (callback) => {
alert('not an image');
}
}
return <div><div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton">
<label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"></label>
</div><input id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} />
<div className="section">
<label><InterfaceText>English Caption</InterfaceText></label>
<input type="text" id="enCaption"/>
</div>
<div className="section">
<label><InterfaceText>Hebrew Caption</InterfaceText></label>
<input type="text" id="heCaption"/>
</div>
</div>
return <div className="section">
<label><InterfaceText>Picture</InterfaceText></label>
<div role="button" title={Sefaria._("Add an image")} aria-label="Add an image" className="editorAddInterfaceButton" contentEditable={false} onClick={(e) => e.stopPropagation()} id="addImageButton">
<label htmlFor="addImageFileSelector" id="addImageFileSelectorLabel"></label>
</div><input id="addImageFileSelector" type="file" onChange={onFileSelect} ref={fileInput} />
{old_filename !== "" && <div style={{"max-width": "420px"}}><br/><ImageWithCaption photoLink={old_filename} caption={caption}/></div>}
</div>
}

const CategoryColorLine = ({category}) =>
Expand Down
22 changes: 17 additions & 5 deletions static/js/TopicEditor.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Sefaria from "./sefaria/sefaria";
import {InterfaceText, requestWithCallBack, ProfilePic} from "./Misc";
import {InterfaceText, requestWithCallBack, ProfilePic, PictureUploader} from "./Misc";
import $ from "./sefaria/sefariaJquery";
import {AdminEditor} from "./AdminEditor";
import {Reorder} from "./CategoryEditor";
Expand All @@ -19,7 +19,9 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => {
birthYear: origData.origBirthYear || "", heDeathPlace: origData.origHeDeathPlace || "",
deathYear: origData.origDeathYear || "", era: origData.origEra || "",
deathPlace: origData.origDeathPlace || "",
picture: origData?.origPicture || ""
enImgCaption: origData?.origImage?.image_caption?.en || "",
heImgCaption: origData?.origImage?.image_caption?.he || "",
image_uri: origData?.origImage?.image_uri || ""
});
const isNew = !('origSlug' in origData);
const [savingStatus, setSavingStatus] = useState(false);
Expand Down Expand Up @@ -68,7 +70,6 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => {
const updateData = function(newData) {
setIsChanged(true);
setData(newData);
console.log(newData);
}
const validate = async function () {
if (!isChanged) {
Expand Down Expand Up @@ -100,6 +101,9 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => {
postData.altTitles.en = data.enAltTitles.map(x => x.name); // alt titles implemented using TitleVariants which contains list of objects with 'name' property.
postData.altTitles.he = data.heAltTitles.map(x => x.name);

if (data.image_uri !== "") {
postData.image = {"image_uri": data.image_uri, "image_caption": {"en": data.enImgCaption, "he": data.heImgCaption}}
}
// add descriptions if they changed
const origDescription = {en: origData?.origEnDescription || "", he: origData?.origHeDescription || ""};
const origCategoryDescription = {en: origData?.origEnCategoryDescription || "", he: origData?.origHeCategoryDescription || ""};
Expand Down Expand Up @@ -155,12 +159,16 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => {
alert("Unfortunately, there may have been an error saving this topic information: " + errorThrown.toString());
});
}
const handlePictureChange = (url) => {
data["image_uri"] = url;
updateData({...data});
}

const deleteObj = function() {
const url = `/api/topic/delete/${data.origSlug}`;
requestWithCallBack({url, type: "DELETE", redirect: () => window.location.href = "/topics"});
}
let items = ["Title", "Hebrew Title", "English Description", "Hebrew Description", "Category Menu", "Picture"];
let items = ["Title", "Hebrew Title", "English Description", "Hebrew Description", "Category Menu"];
if (isCategory) {
items.push("English Short Description");
items.push("Hebrew Short Description");
Expand All @@ -169,10 +177,14 @@ const TopicEditor = ({origData, onCreateSuccess, close, origWasCat}) => {
const authorItems = ["English Alternate Titles", "Hebrew Alternate Titles", "Birth Place", "Hebrew Birth Place", "Birth Year", "Place of Death", "Hebrew Place of Death", "Death Year", "Era"];
authorItems.forEach(x => items.push(x));
}
items.push("English Caption");
items.push("Hebrew Caption");
return <AdminEditor title="Topic Editor" close={close} catMenu={catMenu} data={data} savingStatus={savingStatus}
validate={validate} deleteObj={deleteObj} updateData={updateData} isNew={isNew}
items={items} extras={
[isNew ? null :
[<PictureUploader callback={handlePictureChange} old_filename={data.image_uri}
caption={{en: data.enImgCaption, he: data.heImgCaption}}/>,
isNew ? null :
<Reorder subcategoriesAndBooks={sortedSubtopics}
updateOrder={setSortedSubtopics}
displayType="topics"/>,
Expand Down

0 comments on commit 1bf6cf8

Please sign in to comment.