php 버전 업그레이드

워드프레송 양이 날씨가 더운지 PHP 버전이 너무 낮아서 더 이상 블로그 업데이트 지원을 못해주겠다고 한다. 예전 같으면 불같이 대응을 했겠지만, 지금은 ‘응… 뭐 사용하는데 지장없으니깐 패스..’라는 마음으로 넘기다가 불현듯 과거의 스마트했던.. 아니 지금도 스마트하다던.. 내 모습이 떠올라 겁 없이 이미 설치된 PHP를 제거했다.

yum remove php-*

뜨악! 홈페이지 접속이 안된다!! 내가 뭐랬어? 하지말랬잔아… 아니다. 내가 지금까지 시도했던 것중에 실패한게 하나도 없지 않은가… (정말?) 뭐~ 결국 다음 명령으로 해결함.

yum-config-manager --enable remi-php83
yum install php php-fpm -y
yum install php-mysql
systemctl restart php-fpm

아휴… 뭐래…

시간이 흘러 플러그인 업데이트를 하려고 했더니 “디렉토리를 만들 수 없습니다”라는 에러가 표시되는데, 해결책은 etc/php-fpm.d/www.conf 파일에서 user와 group 값을 apache가 아닌 내가 이용하는 웹서버인 nginx로 변경해야 함.

와…. 검색 엔진으로 해결이 안되다가 그록이한테 물어봐서 해결함. 그록아, 감사해~!

Web Component로 만들어진 사용자 정의 Tag에 스타일 지정하기

AI에게 물어봤더니 방법은 3가지랜다. (AI의 답변을 이렇게 기록해 둔 이유는 답변을 얻기 위한 과정이 단순히 몇번의 프롬프트 입력을 통해 얻지 않았기 때문이다.)

첫째, 호스트 요소(:host) 스타일로 흔히 하듯 사용자 정의 Tag에 대해 스타일을 지정하면 된다.

즉, 아래처럼.

/* style.css */
my-counter {
  display: block; /* 핵심! */
  margin: 20px;
  border: 1px solid red;
  width: 300px;}

사실, 처음엔 제대로 작동하지 않았다. 그 이유는 display에 대한 스타일 값을 지정하지 않았기 때문이다. 사용자 정의 Tag의 display 스타일에 대한 기본값은 미지정이다. 이 display를 지정하지 않으면 padding 이든 margin이든 width든 모두 엉망진창으로 계산되어 반영된다. 이 첫번째가 내가 원했던 답안이다. 그런데 AI는 2가지를 더 제시해줬고 내가 원했던 이 것보다 더 나은 방안이라며 제시했다. 정말 더 나은 방안인지 확인은 해보지 않았으나 일단 정리해 둔다.

두번째는 CSS 커스텀 프로퍼티의 사용이다.

즉, CSS 변수를 사용하라는 건데… 이 방식이 가장 권장되는 방식이라고 한다. AI가 알려준 것을 나열하면 다음과 같다.

컴포넌트 제작자가 내부 스타일의 특정 값을 CSS 변수(Custom Property)로 노출시켜주면, 사용자는 외부에서 이 변수 값을 바꿔서 내부 스타일을 변경할 수 있습니다. 이것이 가장 유연하고 권장되는 “스타일링 API” 설계 방식입니다.

컴포넌트 제작자 (MyCounter.js): 내부 스타일에서 CSS 변수를 사용합니다. var(–변수명, 기본값) 형태로 작성합니다.

// src/MyCounter.js
const templateStyleMyCounter = document.createElement('template');
templateStyleMyCounter.innerHTML = /*html*/ `
  
 `;

컴포넌트 사용자 (style.css): 외부 CSS에서 해당 변수의 값을 재정의합니다.

/* style.css */
my-counter {
  /* MyCounter 내부의 버튼 배경색을 초록색으로 변경 */
  --my-counter-button-bg: green;
  
  /* MyCounter 내부의 span 글자색을 보라색으로 변경 */
  --my-counter-span-color: purple;
}

세번째는 ::part 의사 요소를 사용하는 방법이라고 한다.

컴포넌트 내부 요소를 직접적인 대상으로 스타일을 지정할 수 있다. AI가 설명해준 내용 그대로를 언급하면 다음과 같다.

컴포넌트 제작자가 내부의 특정 요소에 part 속성을 부여하여 외부에 노출시키면, 사용자는 ::part() 의사요소를 사용해 해당 부분의 스타일을 직접 지정할 수 있습니다.

컴포넌트 제작자 (MyCounter.js): 스타일을 지정할 수 있도록 하고 싶은 내부 요소에 part 속성을 추가합니다.

// src/MyCounter.js
const templateDOMMyCounter = document.createElement('template');
templateDOMMyCounter.innerHTML = /*html*/ `
  <div>
    <span part="count-display"></span>  
    <button id="incrementButton" part="increment-button">Increment</button> 
  </div>
`;

컴포넌트 사용자 (style.css): ::part() 선택자를 사용하여 해당 파트의 스타일을 지정합니다.

/* style.css */
/* my-counter 내부의 increment-button 파트의 스타일 지정 */
my-counter::part(increment-button) {
  background-color: orange;
  border-radius: 0;
}

/* my-counter 내부의 count-display 파트의 스타일 지정 */
my-counter::part(count-display) {
  font-style: italic;
  color: red;
}

과연 미래의 소프트웨어 개발은 AI로 인해 얼마나 그 수준이 업그레이드 될 것이며 개발 복잡도는 얼마나 올라갈까…… 향상된 수준과 복잡도를 AI 없이 오직 사람만으로 수용할수 있을까?

MD (Markdown) 문법

# 제목
## 제목
### 제목
#### 제목
##### 제목
###### 제목

이것은 *기울임*입니다.

이것은 **굵은**이지요.

- 첫번째 항목
  - 첫번째 항목에 대해서..
    - 첫번째 항목에 대해서..
      - 첫번째 항목에 대해서
- 두번째 항목
- 세번째 항목
1. 이것은 첫번째
    1. 이것은 첫번째 항목에..
    2. 이것은 첫번째 항모게...
        1. 이것은..
1. 이것은 두번째

[링크](http://www.gisdeveloper.co.kr)입니다.

![이미지](https://upload.wikimedia.org/wikipedia/commons/4/48/Markdown-mark.svg)

코드를 입력해 볼까요.

`console.log('Hello World')`

```js
function test() {
console.log('Hello world!');
}
```

```rust
fn test() {

}
```

```markdown
> 이 문단은 인용된
> 여러 줄
> 중첩된 인용
```

> test
> 안녕하세요
>> 이중중첩
>>> 삼중중첨
>>>> 사중중첩

---
이것은
***
이것도
___
이것은..

|헤더1|헤더2|헤더3|
|:-|-:|:-:|
|*안녕*|**굿**|~~바이~~|
|엘|젤리|쿠|

이것은 \*표\*입니다.

<span style='color:red'>글의 색상을 넣자</span>
글의 색상을 넣자

안녕하세요.<br> 또는 빈칸2개이상
반가습니다.

안녕하세요.
반가습니다.

다중 파일의 드랍에 대한 안전한 처리

웹 API가 계속 발전하고 그에 맞춰 Javascript도 개선되는 과정에서 괴리가 발생하는데요. 그중 한가지 사례로 다중 파일에 대한 드랍처리입니다.

아래의 드랍 처리는 2개 이상의 파일을 드랍했을때 처리되어야할 파일이 누락됩니다.

dropArea.addEventListener('drop', async (e) => {
  e.preventDefault();
  dropArea.classList.remove('highlight');
  fileListElement.innerHTML = '';

  const items = e.dataTransfer.items;
  if (items) {
    for (let i = 0; i < items.length; i++) {
      try {
        const handle = await items[i].getAsFileSystemHandle();

        if (handle.kind === 'file') {
          const file = await handle.getFile();
          displayFile(file);
        } else if (handle.kind === 'directory') {
          await traverseFileSystemHandle(handle);
        }
      } catch (error) {
        console.error(error);
      }
    }
  }
});

위 코드의 문제점에 대한 원인은 비동기 처리를 위해 js에서는 for await .. of 문이 도입되었으나 아직 drop 이벤트 객체의 dataTransfer.items 컬렉션은 이를 지원하지 않기 때문입니다. 차선책으로 이를 개선한 코드가 다음과 같습니다.

dropArea.addEventListener('drop', async (e) => {
  e.preventDefault();
  dropArea.classList.remove('highlight');

  fileListElement.innerHTML = '';

  const items = e.dataTransfer.items;
  if (items) {
    const processingPromises = [];

    for (let i = 0; i < items.length; i++) {
      processingPromises.push((async () => {
        if (items[i].getAsFileSystemHandle) {
          try {
            const handle = await items[i].getAsFileSystemHandle();
            if (handle.kind === 'file') {
              const file = await handle.getFile();
              displayFile(file);
            } else if (handle.kind === 'directory') {
              await traverseFileSystemHandle(handle);
            }
          } catch (error) {
            console.warn(error);
          }
        }
      })()); // 즉시 실행
    }
    await Promise.allSettled(processingPromises);
  }
});