diff --git a/backend/readwise.go b/backend/readwise.go index 8dd8c48..bf091d9 100644 --- a/backend/readwise.go +++ b/backend/readwise.go @@ -14,9 +14,14 @@ import ( "github.com/go-resty/resty/v2" + "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" ) +const ( + HIGHLIGHT_REQUEST_BATCH_MAX = 2000 +) + type Response struct { Highlights []Highlight `json:"highlights"` } @@ -69,22 +74,27 @@ func (r *Readwise) CheckTokenValidity(token string) error { return nil } -func (r *Readwise) SendBookmarks(payload Response, token string) (int, error) { - client := resty.New() - resp, err := client.R(). - SetHeader("Authorization", fmt.Sprintf("Token %s", token)). - SetHeader("User-Agent", UserAgent). - SetBody(payload). - Post(HighlightsEndpoint) - if err != nil { - return 0, fmt.Errorf("failed to send request to Readwise: code %d", resp.StatusCode()) - } - if resp.StatusCode() != 200 { - log.WithFields(log.Fields{"status_code": resp.StatusCode(), "response": string(resp.Body())}).Error("Received a non-200 response from Readwise") - return 0, fmt.Errorf("received a non-200 status code from Readwise: code %d", resp.StatusCode()) +func (r *Readwise) SendBookmarks(payloads []Response, token string) (int, error) { + // TODO: This is dumb, we count stuff that this function doesn't need to know about + we already know the size from earlier + submittedHighlights := 0 + for _, payload := range payloads { + client := resty.New() + resp, err := client.R(). + SetHeader("Authorization", fmt.Sprintf("Token %s", token)). + SetHeader("User-Agent", UserAgent). + SetBody(payload). + Post(HighlightsEndpoint) + if err != nil { + return 0, fmt.Errorf("failed to send request to Readwise: code %d", resp.StatusCode()) + } + if resp.StatusCode() != 200 { + log.WithFields(log.Fields{"status_code": resp.StatusCode(), "response": string(resp.Body())}).Error("Received a non-200 response from Readwise") + return 0, fmt.Errorf("received a non-200 status code from Readwise: code %d", resp.StatusCode()) + } + submittedHighlights += len(payload.Highlights) } - log.WithField("highlight_count", len(payload.Highlights)).Info("Successfully sent bookmarks to Readwise") - return len(payload.Highlights), nil + log.WithField("batch_count", len(payloads)).Info("Successfully sent bookmarks to Readwise") + return submittedHighlights, nil } func (r *Readwise) RetrieveUploadedBooks(token string) (BookListResponse, error) { @@ -157,9 +167,16 @@ func (r *Readwise) UploadCover(encodedCover string, bookId int, token string) er return nil } -func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) (Response, error) { - var payload Response - for _, entry := range bookmarks { +func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) ([]Response, error) { + var payloads []Response + var currentBatch Response + for count, entry := range bookmarks { + // If max payload size is reached, start building another batch which will be sent separately + if count > 0 && (count%HIGHLIGHT_REQUEST_BATCH_MAX == 0) { + fmt.Println(count / HIGHLIGHT_REQUEST_BATCH_MAX) + payloads = append(payloads, currentBatch) + currentBatch = Response{} + } source := contentIndex[entry.VolumeID] log.WithField("title", source.Title).Debug("Parsing highlight") var createdAt string @@ -172,7 +189,7 @@ func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) (Respon t, err := time.Parse("2006-01-02T15:04:05Z", entry.DateModified) if err != nil { log.WithError(err).WithFields(log.Fields{"title": source.Title, "volume_id": entry.VolumeID, "date_modified": entry.DateModified}).Error("Failed to parse a valid timestamp from date modified field") - return Response{}, err + return []Response{}, err } createdAt = t.Format("2006-01-02T15:04:05-07:00") } @@ -180,7 +197,7 @@ func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) (Respon t, err := time.Parse("2006-01-02T15:04:05.000", entry.DateCreated) if err != nil { log.WithError(err).WithFields(log.Fields{"title": source.Title, "volume_id": entry.VolumeID, "date_modified": entry.DateModified}).Error("Failed to parse a valid timestamp from date created field") - return Response{}, err + return []Response{}, err } createdAt = t.Format("2006-01-02T15:04:05-07:00") } @@ -226,12 +243,13 @@ func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) (Respon Note: entry.Annotation, HighlightedAt: createdAt, } - payload.Highlights = append(payload.Highlights, highlight) + currentBatch.Highlights = append(currentBatch.Highlights, highlight) } log.WithFields(log.Fields{"title": source.Title, "volume_id": entry.VolumeID, "chunks": len(highlightChunks)}).Debug("Successfully compiled highlights for book") } - log.WithField("highlight_count", len(payload.Highlights)).Info("Successfully parsed highlights") - return payload, nil + payloads = append(payloads, currentBatch) + log.WithFields(logrus.Fields{"highlight_count": len(currentBatch.Highlights), "batch_count": len(payloads)}).Info("Successfully parsed highlights") + return payloads, nil } func NormaliseText(s string) string { diff --git a/backend/readwise_test.go b/backend/readwise_test.go index 3add474..689e920 100644 --- a/backend/readwise_test.go +++ b/backend/readwise_test.go @@ -13,7 +13,7 @@ func TestBuildPayload_NoBookmarks(t *testing.T) { var contentIndex map[string]Content var bookmarks []Bookmark var actual, _ = BuildPayload(bookmarks, contentIndex) - assert.Equal(t, expected, actual) + assert.Equal(t, expected, actual[0]) } func TestBuildPayload_BookmarksPresent(t *testing.T) { @@ -33,7 +33,7 @@ func TestBuildPayload_BookmarksPresent(t *testing.T) { {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", Text: "Hello World", DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } var actual, _ = BuildPayload(bookmarks, contentIndex) - assert.Equal(t, expected, actual) + assert.Equal(t, expected, actual[0]) } func TestBuildPayload_HandleAnnotationOnly(t *testing.T) { @@ -53,7 +53,7 @@ func TestBuildPayload_HandleAnnotationOnly(t *testing.T) { {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } var actual, _ = BuildPayload(bookmarks, contentIndex) - assert.Equal(t, expected, actual) + assert.Equal(t, expected, actual[0]) } func TestBuildPayload_TitleFallback(t *testing.T) { @@ -73,7 +73,7 @@ func TestBuildPayload_TitleFallback(t *testing.T) { {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", Text: "Hello World", DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } var actual, _ = BuildPayload(bookmarks, contentIndex) - assert.Equal(t, expected, actual) + assert.Equal(t, expected, actual[0]) } func TestBuildPayload_TitleFallbackFailure(t *testing.T) { @@ -91,7 +91,7 @@ func TestBuildPayload_TitleFallbackFailure(t *testing.T) { {VolumeID: "\t", Text: "Hello World", DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } var actual, _ = BuildPayload(bookmarks, contentIndex) - assert.Equal(t, expected, actual) + assert.Equal(t, expected, actual[0]) } func TestBuildPayload_NoHighlightDateCreated(t *testing.T) { @@ -109,7 +109,7 @@ func TestBuildPayload_NoHighlightDateCreated(t *testing.T) { {VolumeID: "\t", Text: "Hello World", DateCreated: "", Annotation: "Making a note here", DateModified: "2006-01-02T15:04:05Z"}, } var actual, _ = BuildPayload(bookmarks, contentIndex) - assert.Equal(t, expected, actual) + assert.Equal(t, expected, actual[0]) } func TestBuildPayload_NoHighlightDateAtAll(t *testing.T) { @@ -118,10 +118,10 @@ func TestBuildPayload_NoHighlightDateAtAll(t *testing.T) { {VolumeID: "abc123", Text: "Hello World", Annotation: "Making a note here"}, } var actual, _ = BuildPayload(bookmarks, contentIndex) - assert.Equal(t, actual.Highlights[0].SourceURL, bookmarks[0].VolumeID) - assert.Equal(t, actual.Highlights[0].Text, bookmarks[0].Text) - assert.Equal(t, actual.Highlights[0].Note, bookmarks[0].Annotation) - assert.NotEmpty(t, actual.Highlights[0].HighlightedAt) + assert.Equal(t, actual[0].Highlights[0].SourceURL, bookmarks[0].VolumeID) + assert.Equal(t, actual[0].Highlights[0].Text, bookmarks[0].Text) + assert.Equal(t, actual[0].Highlights[0].Note, bookmarks[0].Annotation) + assert.NotEmpty(t, actual[0].Highlights[0].HighlightedAt) } func TestBuildPayload_SkipMalformedBookmarks(t *testing.T) { @@ -131,7 +131,7 @@ func TestBuildPayload_SkipMalformedBookmarks(t *testing.T) { {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", DateCreated: "2006-01-02T15:04:05.000"}, } var actual, _ = BuildPayload(bookmarks, contentIndex) - assert.Equal(t, expected, actual) + assert.Equal(t, expected, actual[0]) } func TestBuildPayload_LongHighlightChunks(t *testing.T) { @@ -176,6 +176,19 @@ func TestBuildPayload_LongHighlightChunks(t *testing.T) { bookmarks := []Bookmark{ {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", Text: text, DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } - var actual, _ = BuildPayload(bookmarks, contentIndex) - assert.Equal(t, expected, actual) + actual, err := BuildPayload(bookmarks, contentIndex) + assert.NoError(t, err) + assert.Equal(t, expected, actual[0]) +} + +func TestExcessiveHighlightAmounts(t *testing.T) { + expected := 3 + contentIndex := map[string]Content{"mnt://kobo/blah/Good Book - An Author.epub": {ContentID: "mnt://kobo/blah/Good Book - An Author.epub", Title: "A Book", Attribution: "Computer"}} + bookmarks := []Bookmark{} + for i := 0; i < 4002; i++ { + bookmarks = append(bookmarks, Bookmark{VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", Text: fmt.Sprintf("hi%d", i), DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}) + } + actual, err := BuildPayload(bookmarks, contentIndex) + assert.NoError(t, err) + assert.Equal(t, expected, len(actual)) } diff --git a/frontend/dist/assets/index.37415566.js b/frontend/dist/assets/index.0a4cda25.js similarity index 99% rename from frontend/dist/assets/index.37415566.js rename to frontend/dist/assets/index.0a4cda25.js index 58ddb44..9f8ad77 100644 --- a/frontend/dist/assets/index.37415566.js +++ b/frontend/dist/assets/index.0a4cda25.js @@ -231,7 +231,7 @@ to { * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var ih=k.exports,uh=Symbol.for("react.element"),ah=Symbol.for("react.fragment"),sh=Object.prototype.hasOwnProperty,ch=ih.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,fh={key:!0,ref:!0,__self:!0,__source:!0};function hf(e,t,n){var r,l={},o=null,i=null;n!==void 0&&(o=""+n),t.key!==void 0&&(o=""+t.key),t.ref!==void 0&&(i=t.ref);for(r in t)sh.call(t,r)&&!fh.hasOwnProperty(r)&&(l[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps,t)l[r]===void 0&&(l[r]=t[r]);return{$$typeof:uh,type:e,key:o,ref:i,props:l,_owner:ch.current}}Kl.Fragment=ah;Kl.jsx=hf;Kl.jsxs=hf;du.exports=Kl;const E=du.exports.jsx,M=du.exports.jsxs;function pu(){const e=En();return M("header",{className:"flex p-3",children:[M("div",{className:"w-full text-left",children:[e.pathname==="/overview"&&M(ko,{to:"/",children:[E(lh,{className:"h-5 w-5 inline-block"})," Pick a different device"]}),e.pathname==="/settings"&&M(ko,{to:"/overview",children:[E(rh,{className:"h-5 w-5 inline-block"})," Return to device overview"]})]}),E("div",{className:"w-full text-right",children:e.pathname==="/overview"&&M(ko,{to:"/settings",children:["Settings ",E(oh,{className:"h-5 w-5 inline-block"})]})})]})}var dh="/assets/logo.24674eb3.png";function ph(){return window.go.backend.Backend.DetectKobos()}function mh(){return window.go.backend.Backend.FormatSystemDetails()}function hh(){return window.go.backend.Backend.ForwardToReadwise()}function vh(){return window.go.backend.Backend.GetPlainSystemDetails()}function gh(){return window.go.backend.Backend.GetSelectedKobo()}function yh(){return window.go.backend.Backend.GetSettings()}function wh(){return window.go.backend.Backend.NavigateExplorerToLogLocation()}function Sh(){return window.go.backend.Backend.PromptForLocalDBPath()}function kh(e){return window.go.backend.Backend.SelectKobo(e)}function xh(){const e=Yc(),[t,n]=k.exports.useState([]);k.exports.useEffect(()=>r(),[t.length]);function r(){ph().then(i=>{if(console.log(i),i==null){$("No devices were found");return}$.success(`${i.length} kobos detected`),n(i)}).catch(i=>{$.error(i)})}function l(i){kh(i).then(u=>{u===null?e("/overview"):(console.log(u),$.error("Something went wrong selecting your Kobo"))}).catch(u=>$.error(u))}function o(){Sh().then(i=>{i===null?e("/overview"):(console.log(i),$.error("Something went wrong selecting your local SQLite database"))}).catch(i=>$.error(i))}return M("div",{className:"bg-gray-100 dark:bg-gray-800 ",children:[E(pu,{}),M("div",{className:"min-h-screen flex items-center justify-center pb-24 px-24 grid grid-cols-2 gap-14",children:[M("div",{className:"space-y-2",children:[E("img",{className:"mx-auto h-36 w-auto logo-animation",src:dh,alt:"The October logo, which is a cartoon octopus reading a book."}),E("h2",{className:"text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100",children:"October"}),E("p",{className:"mt-0 text-center text-sm text-gray-600 dark:text-gray-400",children:"Easily access your Kobo highlights"})]}),M("div",{className:"space-y-4 text-center",children:[E("h1",{className:"text-3xl font-bold",children:"Select your Kobo"}),E("button",{onClick:r,children:"Don't see your device? Click here to refresh device list."}),M("ul",{children:[t.map(i=>{let u=`${i.storage} GB \xB7 ${i.display_ppi} PPI`;return i.name||(u="October did not recognise this Kobo but it's safe to continue"),E("li",{children:E("button",{onClick:()=>l(i.mnt_path),className:"w-full bg-purple-200 hover:bg-purple-300 group block rounded-lg p-4 mb-2 cursor-pointer",children:E("dl",{children:M("div",{children:[E("dt",{className:"sr-only",children:"Title"}),E("dd",{className:"border-gray leading-6 font-medium text-black",children:i.name||"Unknown Kobo"}),E("dt",{className:"sr-only",children:"System Specifications"}),E("dd",{className:"text-xs text-gray-600 dark:text-gray-400",children:u})]})})})},i.mnt_path)}),E("li",{children:E("button",{onClick:o,className:"w-full bg-purple-200 hover:bg-purple-300 group block rounded-lg p-4 mb-2 cursor-pointer",children:E("dl",{children:M("div",{children:[E("dt",{className:"sr-only",children:"Title"}),E("dd",{className:"border-gray leading-6 font-medium text-black",children:"Load in a local Kobo database (advanced)"}),E("dt",{className:"sr-only",children:"Description"}),E("dd",{className:"text-xs text-gray-600 dark:text-gray-400",children:"Provide an instance of KoboReader.sqlite3"})]})})})})]})]})]})]})}function Eh(){return window.go.backend.Kobo.CountDeviceBookmarks()}function _h(){return window.go.backend.Settings.CheckReadwiseConfig()}function Ch(e){return window.go.backend.Settings.SaveCoverUploading(e)}function Ph(e){return window.go.backend.Settings.SaveToken(e)}function Nh(e){const[t,n]=k.exports.useState(!1),[r,l]=k.exports.useState({}),[o,i]=k.exports.useState(0);k.exports.useEffect(()=>{gh().then(s=>l(s)).catch(s=>$.error(s))},[r.mnt_path]),k.exports.useEffect(()=>{Eh().then(s=>i(s)).catch(s=>$.error(s))},[o]),k.exports.useEffect(()=>{_h().then(s=>n(s)).catch(s=>$.error(s))});function u(){$("In order to upload to Readwise, you need to configure your token on the Settings page!")}function a(){const s=$.loading("Preparing your highlights...");hh().then(m=>{typeof m=="number"?$.success(`Successfully forwarded ${m} highlights to Readwise`,{id:s}):$.error(`There was a problem sending your highlights: ${m.message}`,{id:s})}).catch(m=>{m.includes("401")?$.error("Received 401 Unauthorised from Readwise. Is your access token correct?",{id:s}):m.includes("failed to upload covers")?$.error(m,{id:s}):$.error(`There was a problem sending your highlights: ${m}`,{id:s})})}return M("div",{className:"bg-gray-100 dark:bg-gray-800 ",children:[E(pu,{}),M("div",{className:"min-h-screen flex items-center justify-center pb-24 px-24 grid grid-cols-2 gap-14",children:[M("div",{className:"space-y-2",children:[E("p",{className:"text-center text-xs text-gray-600 dark:text-gray-400",children:"Currently connected"}),E("h2",{className:"text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100",children:r.name}),r.storage!==0&&r.display_ppi!==0&&M("p",{className:"mt-0 text-center text-sm text-gray-600 dark:text-gray-400",children:[r.storage," GB \xB7 ",r.display_ppi," PPI"]}),r.storage===0&&r.display_ppi===0&&E("p",{className:"mt-0 text-center text-sm text-gray-600 dark:text-gray-400",children:"Dang, that's some hardcore hacker stuff!"})]}),M("div",{className:"space-y-4 text-center",children:[E("h3",{className:"text-md font-medium",children:"What would you like to do?"}),E("ul",{children:E("li",{children:E("button",{onClick:t?a:u,className:"w-full bg-purple-200 hover:bg-purple-300 group block rounded-lg p-4 mb-2 cursor-pointer",children:E("dl",{children:M("div",{children:[E("dt",{className:"sr-only",children:"Title"}),E("dd",{className:"border-gray leading-6 font-medium text-black",children:"Sync your highlights with Readwise"}),E("dt",{className:"sr-only",children:"Description"}),M("dd",{className:"text-xs text-gray-600 dark:text-gray-400",children:["Your Kobo is currently home to ",o," highlights"]})]})})})})})]})]})]})}function La(e){window.runtime.BrowserOpenURL(e)}function Th(e){return window.go.backend.Readwise.CheckTokenValidity(e)}function Oh(){const[e,t]=k.exports.useState(!1),[n,r]=k.exports.useState(""),[l,o]=k.exports.useState(!1),[i,u]=k.exports.useState(""),[a,s]=k.exports.useState("Fetching system details...");k.exports.useEffect(()=>{yh().then(h=>{t(!0),r(h.readwise_token),u(h.readwise_token),o(h.upload_covers)}),vh().then(h=>s(h))},[e]);function m(){r(i),Ph(i),$.success("Your changes have been saved")}function v(){o(!l),Ch(!l),$.success("Your changes have been saved")}function d(){$.promise(Th(i),{loading:"Contacting Readwise...",success:()=>"Your API token is valid!",error:h=>h==="401 Unauthorized"?"Readwise rejected your token":h})}return M("div",{className:"bg-gray-100 dark:bg-gray-800 ",children:[E(pu,{}),M("div",{className:"min-h-screen flex items-center justify-center pb-24 px-24 grid grid-cols-2 gap-14",children:[E("div",{className:"space-y-2",children:E("h2",{className:"text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100",children:"Settings"})}),M("div",{className:"space-y-4",children:[E("div",{className:"bg-white shadow sm:rounded-lg",children:M("div",{className:"px-4 py-5 sm:p-6",children:[E("h3",{className:"text-lg leading-6 font-medium text-gray-900",children:"Set your Readwise access token"}),E("div",{className:"mt-2 max-w-xl text-sm text-gray-500",children:M("p",{children:["You can find your access token at"," ",E("button",{className:"text-gray-600 underline",onClick:()=>La("https://readwise.io/access_token"),children:"https://readwise.io/access_token"})]})}),M("form",{onSubmit:h=>h.preventDefault(),className:"sm:flex flex-col",children:[E("div",{className:"w-full mt-4 sm:flex sm:items-center",children:E("input",{onChange:h=>u(h.target.value),type:"text",name:"token",id:"token",className:"shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md",placeholder:"Your access token goes here",value:i})}),M("div",{className:"w-full mt-4 sm:flex flex-row",children:[E("button",{onClick:d,type:"submit",className:"mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:mt-0 sm:text-sm",children:"Validate"}),E("button",{onClick:m,type:"submit",className:"mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:text-sm",children:"Save"})]})]})]})}),E("div",{className:"shadow overflow-hidden sm:rounded-md",children:E("div",{className:"px-4 py-5 bg-white space-y-6 sm:p-6",children:M("fieldset",{children:[E("legend",{className:"text-base font-medium text-gray-900",children:"Kobo metadata upload"}),E("div",{className:"mt-2 max-w-xl text-sm text-gray-500",children:E("p",{children:a})}),E("div",{className:"mt-4 space-y-4",children:M("div",{className:"flex items-start",children:[E("div",{className:"flex items-center h-5",children:E("input",{onInput:h=>v(!h.currentTarget.checked),checked:l,id:"comments",name:"comments",type:"checkbox",className:"focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"})}),M("div",{className:"ml-3 text-sm",children:[E("label",{htmlFor:"comments",className:"font-medium text-gray-700",children:"Upload covers"}),E("p",{className:"text-gray-500",children:"This will slow down the upload process a bit. It also requires you to have configured Calibre correctly!"})]})]})})]})})}),E("div",{className:"shadow overflow-hidden sm:rounded-md",children:E("div",{className:"px-4 py-5 bg-white space-y-6 sm:p-6",children:M("fieldset",{children:[E("legend",{className:"text-base font-medium text-gray-900",children:"Having trouble?"}),E("div",{className:"space-y-4",children:E("div",{className:"flex items-start",children:M("div",{className:"w-full mt-4 sm:flex flex-row",children:[E("button",{onClick:()=>mh().then(h=>La(`https://github.com/marcus-crane/october/issues/new?body=${encodeURI(`I have an issue with... + */var ih=k.exports,uh=Symbol.for("react.element"),ah=Symbol.for("react.fragment"),sh=Object.prototype.hasOwnProperty,ch=ih.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,fh={key:!0,ref:!0,__self:!0,__source:!0};function hf(e,t,n){var r,l={},o=null,i=null;n!==void 0&&(o=""+n),t.key!==void 0&&(o=""+t.key),t.ref!==void 0&&(i=t.ref);for(r in t)sh.call(t,r)&&!fh.hasOwnProperty(r)&&(l[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps,t)l[r]===void 0&&(l[r]=t[r]);return{$$typeof:uh,type:e,key:o,ref:i,props:l,_owner:ch.current}}Kl.Fragment=ah;Kl.jsx=hf;Kl.jsxs=hf;du.exports=Kl;const E=du.exports.jsx,M=du.exports.jsxs;function pu(){const e=En();return M("header",{className:"flex p-3",children:[M("div",{className:"w-full text-left",children:[e.pathname==="/overview"&&M(ko,{to:"/",children:[E(lh,{className:"h-5 w-5 inline-block"})," Pick a different device"]}),e.pathname==="/settings"&&M(ko,{to:"/overview",children:[E(rh,{className:"h-5 w-5 inline-block"})," Return to device overview"]})]}),E("div",{className:"w-full text-right",children:e.pathname==="/overview"&&M(ko,{to:"/settings",children:["Settings ",E(oh,{className:"h-5 w-5 inline-block"})]})})]})}var dh="/assets/logo.24674eb3.png";function ph(){return window.go.backend.Backend.DetectKobos()}function mh(){return window.go.backend.Backend.FormatSystemDetails()}function hh(){return window.go.backend.Backend.ForwardToReadwise()}function vh(){return window.go.backend.Backend.GetPlainSystemDetails()}function gh(){return window.go.backend.Backend.GetSelectedKobo()}function yh(){return window.go.backend.Backend.GetSettings()}function wh(){return window.go.backend.Backend.NavigateExplorerToLogLocation()}function Sh(){return window.go.backend.Backend.PromptForLocalDBPath()}function kh(e){return window.go.backend.Backend.SelectKobo(e)}function xh(){const e=Yc(),[t,n]=k.exports.useState([]);k.exports.useEffect(()=>r(),[t.length]);function r(){ph().then(i=>{if(console.log(i),i==null){$("No devices were found");return}$.success(`${i.length} kobos detected`),n(i)}).catch(i=>{$.error(i)})}function l(i){kh(i).then(u=>{u===null?e("/overview"):(console.log(u),$.error("Something went wrong selecting your Kobo"))}).catch(u=>$.error(u))}function o(){Sh().then(i=>{i===null?e("/overview"):(console.log(i),$.error("Something went wrong selecting your local SQLite database"))}).catch(i=>$.error(i))}return M("div",{className:"bg-gray-100 dark:bg-gray-800 ",children:[E(pu,{}),M("div",{className:"min-h-screen flex items-center justify-center pb-24 px-24 grid grid-cols-2 gap-14",children:[M("div",{className:"space-y-2",children:[E("img",{className:"mx-auto h-36 w-auto logo-animation",src:dh,alt:"The October logo, which is a cartoon octopus reading a book."}),E("h2",{className:"text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100",children:"October"}),E("p",{className:"mt-0 text-center text-sm text-gray-600 dark:text-gray-400",children:"Easily access your Kobo highlights"})]}),M("div",{className:"space-y-4 text-center",children:[E("h1",{className:"text-3xl font-bold",children:"Select your Kobo"}),E("button",{onClick:r,children:"Don't see your device? Click here to refresh device list."}),M("ul",{children:[t.map(i=>{let u=`${i.storage} GB \xB7 ${i.display_ppi} PPI`;return i.name||(u="October did not recognise this Kobo but it's safe to continue"),E("li",{children:E("button",{onClick:()=>l(i.mnt_path),className:"w-full bg-purple-200 hover:bg-purple-300 group block rounded-lg p-4 mb-2 cursor-pointer",children:E("dl",{children:M("div",{children:[E("dt",{className:"sr-only",children:"Title"}),E("dd",{className:"border-gray leading-6 font-medium text-black",children:i.name||"Unknown Kobo"}),E("dt",{className:"sr-only",children:"System Specifications"}),E("dd",{className:"text-xs text-gray-600 dark:text-gray-400",children:u})]})})})},i.mnt_path)}),E("li",{children:E("button",{onClick:o,className:"w-full bg-purple-200 hover:bg-purple-300 group block rounded-lg p-4 mb-2 cursor-pointer",children:E("dl",{children:M("div",{children:[E("dt",{className:"sr-only",children:"Title"}),E("dd",{className:"border-gray leading-6 font-medium text-black",children:"Load in a local Kobo database (advanced)"}),E("dt",{className:"sr-only",children:"Description"}),E("dd",{className:"text-xs text-gray-600 dark:text-gray-400",children:"Provide an instance of KoboReader.sqlite3"})]})})})})]})]})]})]})}function Eh(){return window.go.backend.Kobo.CountDeviceBookmarks()}function _h(){return window.go.backend.Settings.CheckReadwiseConfig()}function Ch(e){return window.go.backend.Settings.SaveCoverUploading(e)}function Ph(e){return window.go.backend.Settings.SaveToken(e)}function Nh(e){const[t,n]=k.exports.useState(!1),[r,l]=k.exports.useState({}),[o,i]=k.exports.useState(0);k.exports.useEffect(()=>{gh().then(s=>l(s)).catch(s=>$.error(s))},[r.mnt_path]),k.exports.useEffect(()=>{Eh().then(s=>i(s)).catch(s=>$.error(s))},[o]),k.exports.useEffect(()=>{_h().then(s=>n(s)).catch(s=>$.error(s))});function u(){$("In order to upload to Readwise, you need to configure your token on the Settings page!")}function a(){const s=$.loading("Preparing your highlights...");hh().then(m=>{typeof m=="number"?$.success(`Successfully forwarded ${m} highlights to Readwise`,{id:s}):$.error(`There was a problem sending your highlights: ${m.message}`,{id:s})}).catch(m=>{m.includes("401")?$.error("Received 401 Unauthorised from Readwise. Is your access token correct?",{id:s}):m.includes("failed to upload covers")?$.error(m,{id:s}):$.error(`There was a problem sending your highlights: ${m}`,{id:s})})}return M("div",{className:"bg-gray-100 dark:bg-gray-800 ",children:[E(pu,{}),M("div",{className:"min-h-screen flex items-center justify-center pb-24 px-24 grid grid-cols-2 gap-14",children:[M("div",{className:"space-y-2",children:[E("p",{className:"text-center text-xs text-gray-600 dark:text-gray-400",children:"Currently connected"}),E("h2",{className:"text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100",children:r.name}),r.storage!==0&&r.display_ppi!==0&&M("p",{className:"mt-0 text-center text-sm text-gray-600 dark:text-gray-400",children:[r.storage," GB \xB7 ",r.display_ppi," PPI"]}),r.storage===0&&r.display_ppi===0&&E("p",{className:"mt-0 text-center text-sm text-gray-600 dark:text-gray-400",children:"Dang, that's some hardcore hacker stuff!"})]}),M("div",{className:"space-y-4 text-center",children:[E("h3",{className:"text-md font-medium",children:"What would you like to do?"}),E("ul",{children:E("li",{children:E("button",{onClick:t?a:u,className:"w-full bg-purple-200 hover:bg-purple-300 group block rounded-lg p-4 mb-2 cursor-pointer",children:E("dl",{children:M("div",{children:[E("dt",{className:"sr-only",children:"Title"}),E("dd",{className:"border-gray leading-6 font-medium text-black",children:"Sync your highlights with Readwise"}),E("dt",{className:"sr-only",children:"Description"}),M("dd",{className:"text-xs text-gray-600 dark:text-gray-400",children:["Your Kobo is currently home to ",o," highlights"]})]})})})})})]})]})]})}function La(e){window.runtime.BrowserOpenURL(e)}function Th(e){return window.go.backend.Readwise.CheckTokenValidity(e)}function Oh(){const[e,t]=k.exports.useState(!1),[n,r]=k.exports.useState(""),[l,o]=k.exports.useState(!1),[i,u]=k.exports.useState(""),[a,s]=k.exports.useState("Fetching system details...");k.exports.useEffect(()=>{yh().then(h=>{t(!0),r(h.readwise_token),u(h.readwise_token),o(h.upload_covers)}),vh().then(h=>s(h))},[e]);function m(){r(i),Ph(i),$.success("Your changes have been saved")}function v(){o(!l),Ch(!l),$.success("Your changes have been saved")}function d(){$.promise(Th(i),{loading:"Contacting Readwise...",success:()=>"Your API token is valid!",error:h=>h==="401 Unauthorized"?"Readwise rejected your token":h})}return M("div",{className:"bg-gray-100 dark:bg-gray-800 ",children:[E(pu,{}),M("div",{className:"min-h-screen flex items-center justify-center pb-24 px-24 grid grid-cols-2 gap-14",children:[E("div",{className:"space-y-2",children:E("h2",{className:"text-center text-3xl font-extrabold text-gray-900 dark:text-gray-100",children:"Settings"})}),M("div",{className:"space-y-4",children:[E("div",{className:"bg-white shadow sm:rounded-lg",children:M("div",{className:"px-4 py-5 sm:p-6",children:[E("h3",{className:"text-lg leading-6 font-medium text-gray-900",children:"Set your Readwise access token"}),E("div",{className:"mt-2 max-w-xl text-sm text-gray-500",children:M("p",{children:["You can find your access token at"," ",E("button",{className:"text-gray-600 underline",onClick:()=>La("https://readwise.io/access_token"),children:"https://readwise.io/access_token"})]})}),M("form",{onSubmit:h=>h.preventDefault(),className:"sm:flex flex-col",children:[E("div",{className:"w-full mt-4 sm:flex sm:items-center",children:E("input",{onChange:h=>u(h.target.value),type:"text",name:"token",id:"token",className:"shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md",placeholder:"Your access token goes here",value:i})}),M("div",{className:"w-full mt-4 sm:flex flex-row",children:[E("button",{onClick:d,type:"submit",className:"mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:mt-0 sm:text-sm",children:"Validate"}),E("button",{onClick:m,type:"submit",className:"mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:text-sm",children:"Save"})]})]})]})}),E("div",{className:"shadow overflow-hidden sm:rounded-md",children:E("div",{className:"px-4 py-5 bg-white space-y-6 sm:p-6",children:M("fieldset",{children:[E("legend",{className:"text-base font-medium text-gray-900",children:"Kobo metadata upload"}),E("div",{className:"mt-4 space-y-4",children:M("div",{className:"flex items-start",children:[E("div",{className:"flex items-center h-5",children:E("input",{onInput:h=>v(!h.currentTarget.checked),checked:l,id:"comments",name:"comments",type:"checkbox",className:"focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"})}),M("div",{className:"ml-3 text-sm",children:[E("label",{htmlFor:"comments",className:"font-medium text-gray-700",children:"Upload covers"}),E("p",{className:"text-gray-500",children:"This will slow down the upload process a bit. It also requires you to have configured Calibre correctly!"})]})]})})]})})}),E("div",{className:"shadow overflow-hidden sm:rounded-md",children:E("div",{className:"px-4 py-5 bg-white space-y-6 sm:p-6",children:M("fieldset",{children:[E("legend",{className:"text-base font-medium text-gray-900",children:"Having trouble?"}),E("div",{className:"mt-2 max-w-xl text-sm text-gray-500",children:M("p",{children:["October Build: ",a]})}),E("div",{className:"space-y-4",children:E("div",{className:"flex items-start",children:M("div",{className:"w-full mt-4 sm:flex flex-row",children:[E("button",{onClick:()=>mh().then(h=>La(`https://github.com/marcus-crane/october/issues/new?body=${encodeURI(`I have an issue with... --- diff --git a/frontend/dist/index.html b/frontend/dist/index.html index a929cb3..aae13b8 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -4,7 +4,7 @@ October - + diff --git a/frontend/wailsjs/go/backend/Readwise.d.ts b/frontend/wailsjs/go/backend/Readwise.d.ts index 139c3c0..faba30f 100755 --- a/frontend/wailsjs/go/backend/Readwise.d.ts +++ b/frontend/wailsjs/go/backend/Readwise.d.ts @@ -6,6 +6,6 @@ export function CheckTokenValidity(arg1:string):Promise; export function RetrieveUploadedBooks(arg1:string):Promise; -export function SendBookmarks(arg1:backend.Response,arg2:string):Promise; +export function SendBookmarks(arg1:Array,arg2:string):Promise; export function UploadCover(arg1:string,arg2:number,arg3:string):Promise;