WordPress 플러그인 – 이미지 업로드 이슈 수정

옵시디언

WordPress 플러그인을 사용하여 내 옵시디언 노트를 워드프레스로 포스팅을 할 때 이미지가 제대로 업로드가 안되는 이슈가 있다.

항상 안되던 것은 아니였고 되었다 안되었다 하는 것이 내가 사용하는 이미지 태그에 문제가 있거나 플러그인의 버그가 아닐까 싶었다.

이미지 태그의 파싱 이슈


커서 AI의 도움을 받으며 가장 먼저 문제가 될만한 부분을 찾았다.

![[image]]와 같은 옵시디언의 이미지 태그를 파싱하는데 이슈가 있는것으로 보인다.

코드에서의 이미지 태그 파싱은 ![[image|500]] 혹은 ![[image|500x500]]과 같이 크기 지정 옵션만을 사용하는것으로 가정하고 있다.

하지만 나는 이미지 태그를 ![[image|center|500]]과 같이 정렬 옵션까지 추가로 지정해서 사용하고 있다.

이 때문에 이미지의 태그가 제대로 처리되지 않는 것으로 보인다.

커서 AI에게 이 부분에 대해 수정을 요청했다.

markdown-it-image-plugin.ts 의 plugin 함수

기존 코드
function plugin( md : MarkdownIt ) : void
{    
    md.inline.ruler.after( "image", tokenType, ( state, silent ) =>    
    {    
        const regex = /^!\[\[([^|\]\n]+)(\|([^\]\n]+))?\]\]/;
TypeScript
수정된 코드
function plugin( md : MarkdownIt ) : void
{    
    md.inline.ruler.after( "image", tokenType, ( state, silent ) =>    
    {    
        const regex = /^!\[\[([^|\]\n]+)((\|[^|\]\n]+)*)?\]\]/;
TypeScript

가장 먼저 바뀐 부분은 정규식 부분이다.

![[Image|option1|option2]]와 같이 기존에 1개만 처리했던 옵션 부분을 여러개 처리하기 위해 바뀐 부분으로 보인다.

기존 코드
const matched = match[ 0 ];    
const src     = match[ 1 ];    
const size    = match[ 3 ];    
  
let width  : string | undefined;    
let height : string | undefined;    
  
if ( size )    
{    
    const sepIndex = size.indexOf( "x" ); // width x height    
      
	if ( sepIndex > 0 )    
    {    
        width  = trim( size.substring( 0, sepIndex ) );    
        height = trim( size.substring( sepIndex + 1 ) );    
          
        token.attrs = [ [ "src", src ], [ "width", width ], [ "height", height ] ];    
    }    
    else    
    {    
        width = trim( size );    
          
        token.attrs = [ [ "src", src ], [ "width", width ] ];    
    }    
}    
else  
{    
    token.attrs = [ [ "src", src ] ];    
}
TypeScript
수정된 코드
const matched  = match[ 0 ];    
const src      = match[ 1 ];    
const options  = match[ 2 ] ? match[ 2 ].split( '|' ).slice( 1 ) : [];  
  
let width  : string | undefined;    
let height : string | undefined;    
let align  : string | undefined;  
  
options.forEach( opt =>   
{  
    if ( opt === 'center' || opt === 'left' || opt === 'right' )  
    {  
        align = opt;  
    }  
    else if ( /^\d+x\d+$/.test( opt ) )  
    {  
        [ width, height ] = opt.split( 'x' );  
    }  
    else if ( /^\d+$/.test( opt ) )  
    {  
        width = opt;  
    }  
} );  
  
token.attrs = [ [ "src", src ] ];  
  
if ( width )  token.attrs.push( [ "width",  width  ] );  
if ( height ) token.attrs.push( [ "height", height ] );  
if ( align )  token.attrs.push( [ "align",  align  ] );
TypeScript

다음으로 변경된 부분은 옵션부분을 처리한다.

|토큰으로 구분되는 옵션 리스트를 순회하면서 하나씩 처리하는데, 기존 사이즈 외에 정렬에 대한 부분이 추가된 것을 알 수 있다.

기존 코드
md.renderer.rules.ob_img = ( tokens : Token[], idx : number ) =>    
{    
    const token = tokens[ idx ];    
      
    const src    = token.attrs?.[ 0 ]?.[ 1 ];    
    const width  = token.attrs?.[ 1 ]?.[ 1 ];    
    const height = token.attrs?.[ 2 ]?.[ 1 ];    
      
    if ( width )    
    {    
        if ( height )    
        {    
            return `<img src="${ src }" width="${ width }" height="${ height }" alt="">`;    
        }    
        return `<img src="${ src }" width="${ width }" alt="">`;    
    }    
    else    
    {    
        return `<img src="${ src }" alt="">`;    
    }    
};
TypeScript
수정된 코드
md.renderer.rules.ob_img = ( tokens : Token[], idx : number ) =>    
{    
    const token = tokens[ idx ];    
      
    const src    = token.attrs?.[ 0 ]?.[ 1 ];    
      
    const width  = token.attrs?.find( a => a[ 0 ] === 'width'  )?.[ 1 ];    
    const height = token.attrs?.find( a => a[ 0 ] === 'height' )?.[ 1 ];    
    const align  = token.attrs?.find( a => a[ 0 ] === 'align'  )?.[ 1 ];  
      
    let style = '';  
      
    if ( width  ) style += `width:${ width }px;`;  
    if ( height ) style += `height:${ height }px;`;  
      
    let alignClass = '';  
      
    if ( align === 'center' ) alignClass = ' aligncenter';  
    if ( align === 'left'   ) alignClass = ' alignleft';  
    if ( align === 'right'  ) alignClass = ' alignright';  
      
    if ( align )  
    {  
        return `<figure class="wp-block-image${ alignClass }"><img src="${ src }" style="${ style }" alt=""/></figure>`;  
    }  
      
    if ( width )  
    {    
        if ( height )    
        {    
            return `<img src="${ src }" width="${ width }" height="${ height }" alt="">`;    
        }    
        return `<img src="${ src }" width="${ width }" alt="">`;    
    }    
    else    
    {    
        return `<img src="${ src }" alt="">`;    
    }    
};
TypeScript

다음 수정 사항은 html코드의 수정사항이다.

정렬 옵션이 추가된 경우 <figure class="wp-block-image${ alignClass }">와 같은 워드프레스의 블록 지정을 하는 html 코드로 변경된 것이 주요 수정사항이다.

이미지 업로드 코드 이슈


이미지 태그의 파싱 이슈을 해결했음에도 불구하고 여전히 워드프레스에 이미지가 업로드 되지 않았다.

추가로 커서AI를 통해 도움을 받았다.

커서 AI가 진단하는건 이미지를 업로드할때 이미지의 경로를 가져오는 부분에서 옵션 태그부분이 분리되지 않아 이미지 파일을 제대로 찾을 수 없다는 것이였다.

제안한 수정사항은 다음과 같다.

abstract-wp-client.ts 의 updatePostImages 함수

기존 코드
img.src = decodeURI( img.src );  

const fileName = img.src.split( "/" ).pop();  

if ( fileName === undefined )  
{  
    continue;  
}  

const imgFile = this.plugin.app.metadataCache.getFirstLinkpathDest( img.src, fileName );
TypeScript
수정된 코드
img.src = img.src.split( '|' )[ 0 ];  
img.src = decodeURI( img.src );  

const fileName = img.src.split( "/" ).pop();  

if ( fileName === undefined )  
{  
    continue;  
}  

const imgFile = this.plugin.app.metadataCache.getFirstLinkpathDest( img.src, fileName );
TypeScript

차이점은 파일명을 처리하기 이전에 img.src = img.src.split( '|' )[ 0 ];을 통해 이미지 태그 문자열에서 가장 앞부분, 즉 이미지 파일 이름 부분만을 분리해서 처리하는 것이다.

abstract-wp-client.ts 의 getImages 함수

기존 코드
regex = /(!\[\[(.*?)(?:\|(\d+)(?:x(\d+))?)?]])/g;  

while ( ( match = regex.exec( content ) ) !== null )  
{  
    paths.push
    ( {  
        src        : match[ 2 ], 
        original   : match[ 1 ], 
        width      : match[ 3 ], 
        height     : match[ 4 ], 
        startIndex : match.index, 
        endIndex   : match.index + match.length, 
        srcIsUrl   : isValidUrl( match[ 2 ] )  
    } );  
}
TypeScript
수정된 코드
regex = /(!\[\[([^\]]+)\]\])/g;  

while ( ( match = regex.exec( content ) ) !== null )  
{  
    const srcRaw = match[ 2 ];  
    const src = srcRaw.split( '|' )[ 0 ];  
    
    paths.push
    ( {  
        src        : src, 
        original   : match[ 1 ], 
        width      : undefined, 
        height     : undefined, 
        startIndex : match.index, 
        endIndex   : match.index + match.length, 
        srcIsUrl   : isValidUrl( src )  
    } );  
}
TypeScript

기존 코드는 ![[image|500x500]]과 같은 태그만 처리할 수 있었다.

수정된 코드는 일단 ![[]]태그 안의 모든 문자열을 가져와 |구분자로 분할한 뒤 첫번째 문자열을 이미지 경로로 처리한다.

이 수정사항까지 적용해서 빌드 후 플러그인 파일을 교체하면 정상적으로 잘 이미지가 업로드되는 것을 확인할 수 있었다.

커서 AI의 힘을 느낄 수 있었다.

사실 자바 스크립트는 학생때 잠깐 배운거 말고는 기억도 나지 않아 굉장히 생소하다.

그래서 기존 코드 분석에 굉장히 애를 먹을뻔 했다.

하지만 커서 AI는 이슈가 있는 곳도 찾아주고, 수정도 해주고 훌륭하다.

내친김에 커서 AI가 아닌 라이더 AI Assistant도 이정도로 적중률이 괜찮은지 동일한 상태에서 동일한 모델로 같은 질문으로 시도를 해보았다.

비슷하게 이슈가 있는 곳을 찾긴 하는데 수정된 코드의 완성도는 커서 AI보다 확실히 좀 부족하다.

라이더 AI Assistant도 IDE에 포함된 AI라 커서와 비슷한 성능을 내지 않을까 했는데 조금 아쉽긴 하다.

어쨌든 이렇게 이미지 업로드까지 잘 되는 것을 확인 했으니, 하나씩 필요한 것들을 계속 커스텀해봐야겠다.

0 답글

댓글을 남겨주세요

Want to join the discussion?
Feel free to contribute!

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다