@@ -696,51 +696,84 @@ <h1>PyPluginStore: Plugin Store</h1>
696696 return displayName || pluginName ;
697697 }
698698
699- function buildRepoUrl ( author , repo ) {
700- const cleanAuthor = String ( author || '' ) . trim ( ) ;
701- const cleanRepo = String ( repo || '' ) . trim ( ) ;
702- const supportedHosts = [ 'github.com' , 'gitlab.com' , 'codeberg.org' ] ;
703-
704- function stripRepoUrl ( url ) {
705- let stripped = String ( url || '' ) . trim ( ) . replace ( / \/ $ / , '' ) ;
706- const markers = [
707- '/-/tree/' ,
708- '/-/blob/' ,
709- '/tree/' ,
710- '/blob/' ,
711- '/src/branch/' ,
712- '/raw/branch/'
713- ] ;
714- for ( const marker of markers ) {
715- if ( stripped . indexOf ( marker ) !== - 1 ) {
716- stripped = stripped . split ( marker , 1 ) [ 0 ] ;
717- break ;
718- }
699+ function stripRepoUrl ( url ) {
700+ let stripped = String ( url || '' ) . trim ( ) . replace ( / \/ $ / , '' ) ;
701+ const markers = [
702+ '/-/tree/' ,
703+ '/-/blob/' ,
704+ '/tree/' ,
705+ '/blob/' ,
706+ '/src/branch/' ,
707+ '/raw/branch/'
708+ ] ;
709+ for ( const marker of markers ) {
710+ if ( stripped . indexOf ( marker ) !== - 1 ) {
711+ stripped = stripped . split ( marker , 1 ) [ 0 ] ;
712+ break ;
719713 }
720- return stripped . replace ( / \. g i t $ / , '' ) ;
721714 }
715+ return stripped . replace ( / \. g i t $ / , '' ) ;
716+ }
722717
723- function encodePath ( value ) {
724- return String ( value || '' ) . split ( '/' ) . map ( encodeURIComponent ) . join ( '/' ) ;
718+ function encodeRepoPath ( pathParts ) {
719+ return pathParts . map ( part => encodeURIComponent ( part ) ) . join ( '/' ) ;
720+ }
721+
722+ function parseRepoReference ( author , repo ) {
723+ const cleanAuthor = String ( author || '' ) . trim ( ) ;
724+ const cleanRepo = String ( repo || '' ) . trim ( ) . replace ( / \. g i t $ / , '' ) ;
725+ const fallbackHost = 'github.com' ;
726+
727+ if ( cleanAuthor . startsWith ( 'http://' ) || cleanAuthor . startsWith ( 'https://' ) ) {
728+ try {
729+ const strippedUrl = stripRepoUrl ( cleanAuthor ) ;
730+ const parsed = new URL ( strippedUrl ) ;
731+ const pathParts = parsed . pathname
732+ . replace ( / ^ \/ + | \/ + $ / g, '' )
733+ . split ( '/' )
734+ . filter ( Boolean )
735+ . map ( part => decodeURIComponent ( part . replace ( / \. g i t $ / , '' ) ) ) ;
736+ return { host : parsed . hostname . toLowerCase ( ) , pathParts } ;
737+ } catch ( error ) {
738+ return { host : fallbackHost , pathParts : [ cleanAuthor , cleanRepo ] . filter ( Boolean ) } ;
739+ }
725740 }
726741
727- const sshMatch = cleanAuthor . match ( / ^ g i t @ ( [ ^ : ] + ) : ( .+ ) $ / ) ;
728- if ( sshMatch && supportedHosts . indexOf ( sshMatch [ 1 ] . toLowerCase ( ) ) !== - 1 ) {
729- return stripRepoUrl ( 'https://' + sshMatch [ 1 ] . toLowerCase ( ) + '/' + sshMatch [ 2 ] ) ;
742+ const sshMatch = cleanAuthor . match ( / ^ (?: [ ^ @ : / ] + @ ) ? ( [ ^ : / ] + ) : ( .+ ) $ / ) ;
743+ if ( sshMatch ) {
744+ const host = sshMatch [ 1 ] . toLowerCase ( ) ;
745+ const pathParts = sshMatch [ 2 ] . replace ( / \. g i t $ / , '' ) . split ( '/' ) . filter ( Boolean ) ;
746+ return { host, pathParts } ;
730747 }
731748
732- if ( cleanAuthor . startsWith ( 'http://' ) || cleanAuthor . startsWith ( 'https://' ) ) {
733- return stripRepoUrl ( cleanAuthor ) ;
749+ const authorParts = cleanAuthor . split ( '/' ) . filter ( Boolean ) ;
750+ const firstAuthorPart = ( authorParts [ 0 ] || '' ) . toLowerCase ( ) ;
751+ if ( firstAuthorPart . indexOf ( '.' ) !== - 1 && authorParts . length > 1 ) {
752+ const pathParts = authorParts . slice ( 1 ) ;
753+ if ( cleanRepo ) pathParts . push ( cleanRepo ) ;
754+ return { host : firstAuthorPart , pathParts } ;
734755 }
735756
736- for ( const host of supportedHosts ) {
737- if ( cleanAuthor . toLowerCase ( ) === host || cleanAuthor . toLowerCase ( ) . startsWith ( host + '/' ) ) {
738- const path = cleanRepo ? cleanAuthor . replace ( / \/ $ / , '' ) + '/' + cleanRepo : cleanAuthor ;
739- return stripRepoUrl ( 'https://' + encodePath ( path ) ) ;
740- }
757+ return { host : fallbackHost , pathParts : [ cleanAuthor , cleanRepo ] . filter ( Boolean ) } ;
758+ }
759+
760+ function buildRepoUrl ( author , repo ) {
761+ const repoReference = parseRepoReference ( author , repo ) ;
762+ if ( ! repoReference . host || repoReference . pathParts . length === 0 ) {
763+ return '' ;
741764 }
765+ return stripRepoUrl ( 'https://' + repoReference . host + '/' + encodeRepoPath ( repoReference . pathParts ) ) ;
766+ }
742767
743- return 'https://github.com/' + encodeURIComponent ( cleanAuthor ) + '/' + encodeURIComponent ( cleanRepo ) ;
768+ function formatAuthorDisplay ( author , repo ) {
769+ const repoReference = parseRepoReference ( author , repo ) ;
770+ if ( ! repoReference . host || repoReference . pathParts . length === 0 ) {
771+ return String ( author || '' ) . trim ( ) ;
772+ }
773+ const ownerPath = repoReference . pathParts . length > 1
774+ ? repoReference . pathParts . slice ( 0 , - 1 )
775+ : repoReference . pathParts ;
776+ return repoReference . host + '/' + ownerPath . join ( '/' ) ;
744777 }
745778
746779 function renderPlugins ( pluginData , installedList ) {
@@ -844,7 +877,7 @@ <h1>PyPluginStore: Plugin Store</h1>
844877
845878 const meta = document . createElement ( 'div' ) ;
846879 meta . className = 'card-meta' ;
847- meta . appendChild ( document . createTextNode ( 'Author: ' + author ) ) ;
880+ meta . appendChild ( document . createTextNode ( 'Author: ' + formatAuthorDisplay ( author , repo ) ) ) ;
848881 meta . appendChild ( document . createElement ( 'br' ) ) ;
849882 meta . appendChild ( document . createTextNode ( 'Repo: ' + repo + ( branch ? ' (' + branch + ')' : '' ) ) ) ;
850883
0 commit comments