@@ -13,8 +13,8 @@ document.addEventListener("DOMContentLoaded", function () {
1313 const markdownEditor = document . getElementById ( "markdown-editor" ) ;
1414 const markdownPreview = document . getElementById ( "markdown-preview" ) ;
1515 const themeToggle = document . getElementById ( "theme-toggle" ) ;
16- const importButton = document . getElementById ( "import-button " ) ;
17- const importGithubButton = document . getElementById ( "import-github-button " ) ;
16+ const importFromFileButton = document . getElementById ( "import-from-file " ) ;
17+ const importFromGithubButton = document . getElementById ( "import-from-github " ) ;
1818 const fileInput = document . getElementById ( "file-input" ) ;
1919 const exportMd = document . getElementById ( "export-md" ) ;
2020 const exportHtml = document . getElementById ( "export-html" ) ;
@@ -61,6 +61,13 @@ document.addEventListener("DOMContentLoaded", function () {
6161 const mobileThemeToggle = document . getElementById ( "mobile-theme-toggle" ) ;
6262 const shareButton = document . getElementById ( "share-button" ) ;
6363 const mobileShareButton = document . getElementById ( "mobile-share-button" ) ;
64+ const githubImportModal = document . getElementById ( "github-import-modal" ) ;
65+ const githubImportTitle = document . getElementById ( "github-import-title" ) ;
66+ const githubImportUrlInput = document . getElementById ( "github-import-url" ) ;
67+ const githubImportFileSelect = document . getElementById ( "github-import-file-select" ) ;
68+ const githubImportError = document . getElementById ( "github-import-error" ) ;
69+ const githubImportCancelBtn = document . getElementById ( "github-import-cancel" ) ;
70+ const githubImportSubmitBtn = document . getElementById ( "github-import-submit" ) ;
6471
6572 // Check dark mode preference first for proper initialization
6673 const prefersDarkMode =
@@ -918,71 +925,165 @@ This is a fully client-side application. Your content never leaves your browser
918925 }
919926
920927 function setGitHubImportLoading ( isLoading ) {
921- [ importGithubButton , mobileImportGithubBtn ] . forEach ( ( btn ) => {
922- if ( ! btn ) return ;
923- if ( ! btn . dataset . originalText ) {
924- btn . dataset . originalText = btn . innerHTML ;
925- }
926- btn . disabled = isLoading ;
927- btn . innerHTML = isLoading ? '<i class="bi bi-hourglass-split"></i> Importing...' : btn . dataset . originalText ;
928- } ) ;
928+ if ( ! githubImportSubmitBtn ) return ;
929+ if ( isLoading ) {
930+ githubImportSubmitBtn . dataset . loadingText = githubImportSubmitBtn . textContent ;
931+ githubImportSubmitBtn . textContent = "Importing..." ;
932+ } else if ( githubImportSubmitBtn . dataset . loadingText ) {
933+ githubImportSubmitBtn . textContent = githubImportSubmitBtn . dataset . loadingText ;
934+ delete githubImportSubmitBtn . dataset . loadingText ;
935+ }
936+ }
937+
938+ function setGitHubImportMessage ( message , options = { } ) {
939+ if ( ! githubImportError ) return ;
940+ const { isError = true } = options ;
941+ githubImportError . classList . toggle ( "is-info" , ! isError ) ;
942+ if ( ! message ) {
943+ githubImportError . textContent = "" ;
944+ githubImportError . style . display = "none" ;
945+ return ;
946+ }
947+ githubImportError . textContent = message ;
948+ githubImportError . style . display = "block" ;
949+ }
950+
951+ function resetGitHubImportModal ( ) {
952+ if ( ! githubImportUrlInput || ! githubImportFileSelect || ! githubImportSubmitBtn ) return ;
953+ if ( githubImportTitle ) {
954+ githubImportTitle . textContent = "Import Markdown from GitHub" ;
955+ }
956+ githubImportUrlInput . value = "" ;
957+ githubImportUrlInput . style . display = "block" ;
958+ githubImportUrlInput . disabled = false ;
959+ githubImportFileSelect . innerHTML = "" ;
960+ githubImportFileSelect . style . display = "none" ;
961+ githubImportFileSelect . disabled = false ;
962+ githubImportSubmitBtn . dataset . step = "url" ;
963+ delete githubImportSubmitBtn . dataset . owner ;
964+ delete githubImportSubmitBtn . dataset . repo ;
965+ delete githubImportSubmitBtn . dataset . ref ;
966+ githubImportSubmitBtn . textContent = "Import" ;
967+ setGitHubImportMessage ( "" ) ;
968+ }
969+
970+ function openGitHubImportModal ( ) {
971+ if ( ! githubImportModal || ! githubImportUrlInput || ! githubImportSubmitBtn ) return ;
972+ resetGitHubImportModal ( ) ;
973+ githubImportModal . style . display = "flex" ;
974+ githubImportUrlInput . focus ( ) ;
929975 }
930976
931- async function importMarkdownFromGitHub ( ) {
932- const urlInput = prompt ( "Enter a GitHub repository, folder, or Markdown file URL:" ) ;
933- if ( ! urlInput ) return ;
977+ function closeGitHubImportModal ( ) {
978+ if ( ! githubImportModal ) return ;
979+ githubImportModal . style . display = "none" ;
980+ resetGitHubImportModal ( ) ;
981+ }
982+
983+ async function handleGitHubImportSubmit ( ) {
984+ if ( ! githubImportSubmitBtn || ! githubImportUrlInput || ! githubImportFileSelect ) return ;
985+ const setGitHubImportDialogDisabled = ( disabled ) => {
986+ githubImportSubmitBtn . disabled = disabled ;
987+ if ( githubImportCancelBtn ) {
988+ githubImportCancelBtn . disabled = disabled ;
989+ }
990+ } ;
991+ const step = githubImportSubmitBtn . dataset . step || "url" ;
992+ if ( step === "select" ) {
993+ const selectedPath = githubImportFileSelect . value ;
994+ const owner = githubImportSubmitBtn . dataset . owner ;
995+ const repo = githubImportSubmitBtn . dataset . repo ;
996+ const ref = githubImportSubmitBtn . dataset . ref ;
997+ if ( ! owner || ! repo || ! ref || ! selectedPath ) {
998+ setGitHubImportMessage ( "Please select a file to import." ) ;
999+ return ;
1000+ }
1001+ setGitHubImportLoading ( true ) ;
1002+ setGitHubImportDialogDisabled ( true ) ;
1003+ try {
1004+ const markdown = await fetchTextContent ( buildRawGitHubUrl ( owner , repo , ref , selectedPath ) ) ;
1005+ newTab ( markdown , getFileName ( selectedPath ) . replace ( / \. ( m d | m a r k d o w n ) $ / i, "" ) ) ;
1006+ closeGitHubImportModal ( ) ;
1007+ } catch ( error ) {
1008+ console . error ( "GitHub import failed:" , error ) ;
1009+ setGitHubImportMessage ( "GitHub import failed: " + error . message ) ;
1010+ } finally {
1011+ setGitHubImportDialogDisabled ( false ) ;
1012+ setGitHubImportLoading ( false ) ;
1013+ }
1014+ return ;
1015+ }
1016+
1017+ const urlInput = githubImportUrlInput . value . trim ( ) ;
1018+ if ( ! urlInput ) {
1019+ setGitHubImportMessage ( "Please enter a GitHub URL." ) ;
1020+ return ;
1021+ }
9341022
9351023 const parsed = parseGitHubImportUrl ( urlInput ) ;
9361024 if ( ! parsed || ! parsed . owner || ! parsed . repo ) {
937- alert ( "Please enter a valid GitHub URL." ) ;
1025+ setGitHubImportMessage ( "Please enter a valid GitHub URL." ) ;
9381026 return ;
9391027 }
9401028
1029+ setGitHubImportMessage ( "" ) ;
9411030 setGitHubImportLoading ( true ) ;
1031+ setGitHubImportDialogDisabled ( true ) ;
9421032 try {
9431033 if ( parsed . type === "file" ) {
9441034 if ( ! isMarkdownPath ( parsed . filePath ) ) {
9451035 throw new Error ( "The provided URL does not point to a Markdown file." ) ;
9461036 }
9471037 const markdown = await fetchTextContent ( buildRawGitHubUrl ( parsed . owner , parsed . repo , parsed . ref , parsed . filePath ) ) ;
9481038 newTab ( markdown , getFileName ( parsed . filePath ) . replace ( / \. ( m d | m a r k d o w n ) $ / i, "" ) ) ;
1039+ closeGitHubImportModal ( ) ;
9491040 return ;
9501041 }
9511042
9521043 const ref = parsed . ref || await getDefaultBranch ( parsed . owner , parsed . repo ) ;
9531044 const files = await listMarkdownFiles ( parsed . owner , parsed . repo , ref , parsed . basePath || "" ) ;
9541045
9551046 if ( ! files . length ) {
956- alert ( "No Markdown files were found at that GitHub location." ) ;
1047+ setGitHubImportMessage ( "No Markdown files were found at that GitHub location." ) ;
9571048 return ;
9581049 }
9591050
960- let targetPath = files [ 0 ] ;
961- if ( files . length > 1 ) {
962- const maxShown = Math . min ( files . length , MAX_GITHUB_FILES_SHOWN ) ;
963- const choices = files
964- . slice ( 0 , maxShown )
965- . map ( ( file , index ) => `${ index + 1 } . ${ file } ` )
966- . join ( "\n" ) ;
967- const choice = prompt (
968- `Found ${ files . length } Markdown files.\nEnter a number to import (showing first ${ maxShown } ):\n\n${ choices } ${ files . length > maxShown ? "\n..." : "" } ` ,
969- "1"
970- ) ;
971-
972- if ( choice === null ) return ;
973- const selectedIndex = Number ( choice ) - 1 ;
974- if ( ! Number . isInteger ( selectedIndex ) || selectedIndex < 0 || selectedIndex >= maxShown ) {
975- throw new Error ( `Please enter a number between 1 and ${ maxShown } (from the displayed list).` ) ;
976- }
977- targetPath = files [ selectedIndex ] ;
1051+ const shownFiles = files . slice ( 0 , MAX_GITHUB_FILES_SHOWN ) ;
1052+ if ( files . length === 1 ) {
1053+ const targetPath = files [ 0 ] ;
1054+ const markdown = await fetchTextContent ( buildRawGitHubUrl ( parsed . owner , parsed . repo , ref , targetPath ) ) ;
1055+ newTab ( markdown , getFileName ( targetPath ) . replace ( / \. ( m d | m a r k d o w n ) $ / i, "" ) ) ;
1056+ closeGitHubImportModal ( ) ;
1057+ return ;
9781058 }
9791059
980- const markdown = await fetchTextContent ( buildRawGitHubUrl ( parsed . owner , parsed . repo , ref , targetPath ) ) ;
981- newTab ( markdown , getFileName ( targetPath ) . replace ( / \. ( m d | m a r k d o w n ) $ / i, "" ) ) ;
1060+ githubImportUrlInput . style . display = "none" ;
1061+ githubImportFileSelect . style . display = "block" ;
1062+ githubImportFileSelect . innerHTML = "" ;
1063+ shownFiles . forEach ( ( filePath ) => {
1064+ const option = document . createElement ( "option" ) ;
1065+ option . value = filePath ;
1066+ option . textContent = filePath ;
1067+ githubImportFileSelect . appendChild ( option ) ;
1068+ } ) ;
1069+ if ( files . length > MAX_GITHUB_FILES_SHOWN ) {
1070+ setGitHubImportMessage ( `Showing first ${ MAX_GITHUB_FILES_SHOWN } of ${ files . length } Markdown files.` , { isError : false } ) ;
1071+ } else {
1072+ setGitHubImportMessage ( "" ) ;
1073+ }
1074+ if ( githubImportTitle ) {
1075+ githubImportTitle . textContent = "Select a Markdown file to import" ;
1076+ }
1077+ githubImportSubmitBtn . dataset . step = "select" ;
1078+ githubImportSubmitBtn . dataset . owner = parsed . owner ;
1079+ githubImportSubmitBtn . dataset . repo = parsed . repo ;
1080+ githubImportSubmitBtn . dataset . ref = ref ;
1081+ githubImportSubmitBtn . textContent = "Import Selected" ;
9821082 } catch ( error ) {
9831083 console . error ( "GitHub import failed:" , error ) ;
984- alert ( "GitHub import failed: " + error . message ) ;
1084+ setGitHubImportMessage ( "GitHub import failed: " + error . message ) ;
9851085 } finally {
1086+ setGitHubImportDialogDisabled ( false ) ;
9861087 setGitHubImportLoading ( false ) ;
9871088 }
9881089 }
@@ -1317,7 +1418,8 @@ This is a fully client-side application. Your content never leaves your browser
13171418 } ) ;
13181419 mobileImportBtn . addEventListener ( "click" , ( ) => fileInput . click ( ) ) ;
13191420 mobileImportGithubBtn . addEventListener ( "click" , ( ) => {
1320- importMarkdownFromGitHub ( ) . finally ( closeMobileMenu ) ;
1421+ closeMobileMenu ( ) ;
1422+ openGitHubImportModal ( ) ;
13211423 } ) ;
13221424 mobileExportMd . addEventListener ( "click" , ( ) => exportMd . click ( ) ) ;
13231425 mobileExportHtml . addEventListener ( "click" , ( ) => exportHtml . click ( ) ) ;
@@ -1417,13 +1519,40 @@ This is a fully client-side application. Your content never leaves your browser
14171519 renderMarkdown ( ) ;
14181520 } ) ;
14191521
1420- importButton . addEventListener ( "click" , function ( ) {
1421- fileInput . click ( ) ;
1422- } ) ;
1522+ if ( importFromFileButton ) {
1523+ importFromFileButton . addEventListener ( "click" , function ( e ) {
1524+ e . preventDefault ( ) ;
1525+ fileInput . click ( ) ;
1526+ } ) ;
1527+ }
14231528
1424- importGithubButton . addEventListener ( "click" , function ( ) {
1425- importMarkdownFromGitHub ( ) ;
1426- } ) ;
1529+ if ( importFromGithubButton ) {
1530+ importFromGithubButton . addEventListener ( "click" , function ( e ) {
1531+ e . preventDefault ( ) ;
1532+ openGitHubImportModal ( ) ;
1533+ } ) ;
1534+ }
1535+
1536+ if ( githubImportSubmitBtn ) {
1537+ githubImportSubmitBtn . addEventListener ( "click" , handleGitHubImportSubmit ) ;
1538+ }
1539+ if ( githubImportCancelBtn ) {
1540+ githubImportCancelBtn . addEventListener ( "click" , closeGitHubImportModal ) ;
1541+ }
1542+ const handleGitHubImportInputKeydown = function ( e ) {
1543+ if ( e . key === "Enter" ) {
1544+ e . preventDefault ( ) ;
1545+ handleGitHubImportSubmit ( ) ;
1546+ } else if ( e . key === "Escape" ) {
1547+ closeGitHubImportModal ( ) ;
1548+ }
1549+ } ;
1550+ if ( githubImportUrlInput ) {
1551+ githubImportUrlInput . addEventListener ( "keydown" , handleGitHubImportInputKeydown ) ;
1552+ }
1553+ if ( githubImportFileSelect ) {
1554+ githubImportFileSelect . addEventListener ( "keydown" , handleGitHubImportInputKeydown ) ;
1555+ }
14271556
14281557 fileInput . addEventListener ( "change" , function ( e ) {
14291558 const file = e . target . files [ 0 ] ;
0 commit comments