@@ -1166,6 +1166,7 @@ const els = {
11661166 yarnEditTitle : document . querySelector ( "#yarnEditTitle" ) ,
11671167 stashList : document . querySelector ( "#stashList" ) ,
11681168 stockTypeTabs : document . querySelector ( "#stockTypeTabs" ) ,
1169+ importStashBtn : document . querySelector ( "#importStashBtn" ) ,
11691170 colorCardYarnBtn : document . querySelector ( "#colorCardYarnBtn" ) ,
11701171 bulkYarnBtn : document . querySelector ( "#bulkYarnBtn" ) ,
11711172 newYarnBtn : document . querySelector ( "#newYarnBtn" ) ,
@@ -3033,13 +3034,21 @@ async function signOutGoogle() {
30333034 await firebaseAuth . signOut ( ) ;
30343035}
30353036
3036- function patternCloudShareUrl ( dataUrl ) {
3037- const url = new URL ( location . href ) ;
3037+ function shareBaseUrl ( ) {
3038+ return PUBLIC_ASSET_BASE ;
3039+ }
3040+
3041+ function shareUrlWithParam ( key , value ) {
3042+ const url = new URL ( shareBaseUrl ( ) ) ;
30383043 url . hash = "" ;
3039- url . searchParams . set ( "importPatternUrl" , dataUrl ) ;
3044+ url . searchParams . set ( key , value ) ;
30403045 return url . toString ( ) ;
30413046}
30423047
3048+ function patternCloudShareUrl ( dataUrl ) {
3049+ return shareUrlWithParam ( "importPatternUrl" , dataUrl ) ;
3050+ }
3051+
30433052function extractPatternImportUrl ( text ) {
30443053 const value = String ( text || "" ) . trim ( ) ;
30453054 if ( ! value ) return "" ;
@@ -3116,7 +3125,7 @@ async function checkSharedPatternImportUrl() {
31163125
31173126async function sharePattern ( pattern ) {
31183127 const payload = patternSharePackage ( pattern ) ;
3119- const shareUrl = `${ location . origin } ${ location . pathname } #pattern=${ encodeSharePayload ( payload ) } ` ;
3128+ const shareUrl = `${ shareBaseUrl ( ) } #pattern=${ encodeSharePayload ( payload ) } ` ;
31203129 const text = `匯入「${ pattern . name } 」織圖:${ shareUrl } ` ;
31213130 const canUseLink = shareUrl . length < 1800 ;
31223131 if ( canUseLink && navigator . share ) {
@@ -3149,6 +3158,30 @@ function stashSharePackage(items) {
31493158 } ;
31503159}
31513160
3161+ function stashCloudShareUrl ( dataUrl ) {
3162+ return shareUrlWithParam ( "importStashUrl" , dataUrl ) ;
3163+ }
3164+
3165+ function extractStashImportUrl ( text ) {
3166+ const value = String ( text || "" ) . trim ( ) ;
3167+ if ( ! value ) return "" ;
3168+ try {
3169+ const url = new URL ( value ) ;
3170+ const embedded = url . searchParams . get ( "importStashUrl" ) || url . searchParams . get ( "stashUrl" ) ;
3171+ if ( embedded ) return embedded ;
3172+ if ( url . hostname . includes ( "res.cloudinary.com" ) ) return url . toString ( ) ;
3173+ } catch { }
3174+ return "" ;
3175+ }
3176+
3177+ async function readStashImportText ( textOrUrl ) {
3178+ const importUrl = extractStashImportUrl ( textOrUrl ) ;
3179+ if ( ! importUrl ) return normalizeClipboardJsonText ( textOrUrl ) ;
3180+ const response = await fetch ( importUrl ) ;
3181+ if ( ! response . ok ) throw new Error ( "無法讀取庫存分享資料" ) ;
3182+ return normalizeClipboardJsonText ( await response . text ( ) ) ;
3183+ }
3184+
31523185function importStashPackage ( payload ) {
31533186 const items = payload ?. type === "gogo-stash" ? payload . items : payload ?. items || [ ] ;
31543187 if ( ! Array . isArray ( items ) || ! items . length ) throw new Error ( "不是可匯入的庫存連結" ) ;
@@ -3163,27 +3196,41 @@ function importStashPackage(payload) {
31633196 return normalized ;
31643197}
31653198
3199+ function finishImportedStash ( imported ) {
3200+ activeStockType = imported [ 0 ] ?. stockType || activeStockType ;
3201+ selectedYarnId = imported [ 0 ] ?. id || selectedYarnId ;
3202+ selectedYarnIds . clear ( ) ;
3203+ switchView ( "stash" ) ;
3204+ alert ( `已匯入 ${ imported . length } 個庫存項目。` ) ;
3205+ }
3206+
3207+ async function importStashText ( textOrUrl ) {
3208+ const text = await readStashImportText ( textOrUrl ) ;
3209+ const imported = importStashPackage ( JSON . parse ( text ) ) ;
3210+ finishImportedStash ( imported ) ;
3211+ }
3212+
3213+ async function importStashFromClipboard ( ) {
3214+ try {
3215+ const text = await navigator . clipboard . readText ( ) ;
3216+ await importStashText ( text ) ;
3217+ } catch ( error ) {
3218+ alert ( `剪貼簿內容不是可匯入的庫存資料:${ error . message || "請確認已複製庫存分享連結" } ` ) ;
3219+ }
3220+ }
3221+
31663222async function shareStashItems ( items ) {
31673223 const payload = stashSharePackage ( items ) ;
3168- const shareUrl = `${ location . origin } ${ location . pathname } #stash=${ encodeSharePayload ( payload ) } ` ;
31693224 const title = items . length === 1 ? `匯入「${ items [ 0 ] . colorName } 」庫存` : `匯入 ${ items . length } 個庫存項目` ;
3170- if ( shareUrl . length > 6000 ) {
3171- alert ( "選取的庫存資料太多或含圖片,連結會太長。請少選幾個,或先移除圖片後再分享。" ) ;
3172- return ;
3173- }
3174- if ( navigator . share ) {
3175- try {
3176- await navigator . share ( { title, text : title , url : shareUrl } ) ;
3177- return ;
3178- } catch ( error ) {
3179- if ( error . name === "AbortError" ) return ;
3180- }
3181- }
3225+ const text = JSON . stringify ( payload , null , 2 ) ;
3226+ const filename = items . length === 1 ? `${ items [ 0 ] ?. colorName || "gogo-stash" } .txt` : `${ items . length } -stash-items.txt` ;
31823227 try {
3183- await navigator . clipboard ?. writeText ( shareUrl ) ;
3184- alert ( "分享連結已複製。" ) ;
3185- } catch {
3186- prompt ( "複製這個庫存分享連結:" , shareUrl ) ;
3228+ const dataUrl = await uploadTextToCloudinary ( text , filename ) ;
3229+ const shareUrl = stashCloudShareUrl ( dataUrl ) ;
3230+ await navigator . clipboard . writeText ( shareUrl ) ;
3231+ alert ( `${ title } 的連結已複製,可以貼到 LINE 或其他裝置匯入。` ) ;
3232+ } catch ( error ) {
3233+ alert ( `庫存分享失敗:${ error . message || "請稍後再試" } ` ) ;
31873234 }
31883235}
31893236
@@ -3207,6 +3254,13 @@ function checkSharedPatternFromUrl() {
32073254}
32083255
32093256function checkSharedStashFromUrl ( ) {
3257+ const importUrl = new URLSearchParams ( location . search ) . get ( "importStashUrl" ) ;
3258+ if ( importUrl ) {
3259+ importStashText ( importUrl )
3260+ . catch ( ( error ) => alert ( `庫存分享連結無法匯入:${ error . message || "請確認連結是否正確" } ` ) )
3261+ . finally ( ( ) => history . replaceState ( null , "" , location . pathname + location . hash ) ) ;
3262+ return ;
3263+ }
32103264 const match = location . hash . match ( / ^ # s t a s h = ( .+ ) $ / ) ;
32113265 if ( ! match ) return ;
32123266 try {
@@ -5732,6 +5786,7 @@ els.stockTypeTabs.addEventListener("click", (event) => {
57325786 selectedYarnIds . clear ( ) ;
57335787 render ( ) ;
57345788} ) ;
5789+ els . importStashBtn ?. addEventListener ( "click" , importStashFromClipboard ) ;
57355790els . yarnImageInput . addEventListener ( "change" , ( ) => readImages ( els . yarnImageInput . files , addYarnImage ) ) ;
57365791attachCoverImageHandlers ( els . yarnImagePreview , "[data-yarn-image]" , ( ) => state . yarns . find ( ( item ) => item . id === selectedYarnId ) , "yarn" ) ;
57375792els . yarnImagePreview . addEventListener ( "click" , ( event ) => {
0 commit comments