201

(10 답글들, 잡담하기에 작성)

마루지기님, Novlex님 IE Tab을 설치해보세요. smile

202

(148 답글들, 스킨 및 플러그인에 작성)

J. Parker 작성:

ghost_ghost님 말씀처럼 CDATA처리를 하니 ']]</caption>'이하부터는 잘못출력이 됩니다.

오타신거 같네요.
]]></caption>이 되어야겠죠. smile

daybreaker 작성:

아주 짤막하게 개념만 설명하려고 했던 제 설명과 함수 만들 때의 주의사항까지 알려주시는 Peris님의 설명...
너무나 대조적이군요....;;;

;; 근데.. 저렇게 장황하게 써놓아봤자 오히려 더 헷갈리기만 할지도 모른다는 생각이 드네요. OTL;;

204

(17 답글들, 잡담하기에 작성)

나니 작성:
graphittie 작성:

개인차가 있는 문제겠지만, 경상도 말은 좀 무섭습니다. 화내는 것처럼 들린다고 할까... 큰 누님의 시댁이 경상도 쪽이라 조금 익숙해지기는 했지만, 실생활에서 경상도 말을 지속적으로 접하지 않는 분이시라면 어감상 조금 거부감이 들 수도 있을 것 같습니다. 이것도 편견이라면 편견이겠지만, 이 편견을 깰 수 있을 정도로 경상도 말에 익숙해지는데 필요한 시간이... 너무 깁니다.:|

개인차가 있는 문제지만 제주억양도 화내는 억양입니다 wink
(최대한 부드럽게 말해야 희화됩니다 하하;; llorz)

제주는 억양을 떠나서 의미 자체를 알아 들을 수가.. OTL;;

다른 메뉴들에서는 "~니다."를 사용하는데 그 부분만 그렇게 하는 것은 안어울린다고 생각합니다.

"사용자에 의해 수정되었습니다." 정도가 좋지 않을까요?

206

(3 답글들, 아이디어 및 기능 제안에 작성)

結!者!解!之!! big_smile

다중사용자 모드일 경우를 생각해서 하신 말씀이신건가요?

아 그리고 어떤 이벤트가 있는지는 http://forum.tattertools.com/ko/viewtopic.php?id=380 를 참고해주세요. smile

환영합니다. smile

플러그인의 작동원리는 생각보다 어렵지는 않습니다.
곧 나올 1.1도 크게 차이나지는 않지만 1.0.6.1 기준으로 설명드리겠습니다.

blog/index.php 파일을 열어보시면 1024 line ~ 1093 line이 플러그인과 관련된 부분입니다.

$activePlugins=array();
$eventMappings=array();
$tagMappings=array();
if(!empty($owner)){
    $activePlugins=fetchQueryColumn("SELECT name FROM {$database['prefix']}Plugins WHERE owner = $owner");
    $xmls=new XMLStruct();
    foreach($activePlugins as $plugin){
        $manifest=@file_get_contents("../plugins/$plugin/index.xml");
        if($manifest&&$xmls->open($manifest)){
            if($xmls->doesExist('/plugin/binding/listener')){
                foreach($xmls->selectNodes('/plugin/binding/listener') as $listener){
                    if(!empty($listener['.attributes']['event'])&&!empty($listener['.value'])){
                        if(!isset($eventMappings[$listener['.attributes']['event']]))
                            $eventMappings[$listener['.attributes']['event']]=array();
                        array_push($eventMappings[$listener['.attributes']['event']],array('plugin'=>$plugin,'listener'=>$listener['.value']));
                    }
                }
                unset($listener);
            }
            if($xmls->doesExist('/plugin/binding/tag')){
                foreach($xmls->selectNodes('/plugin/binding/tag') as $tag){
                    if(!empty($tag['.attributes']['name'])&&!empty($tag['.attributes']['handler'])){
                        if(!isset($tagMappings[$tag['.attributes']['name']]))
                            $tagMappings[$tag['.attributes']['name']]=array();
                        array_push($tagMappings[$tag['.attributes']['name']],array('plugin'=>$plugin,'handler'=>$tag['.attributes']['handler']));
                    }
                }
                unset($tag);
            }
        }else{
            $plugin=mysql_escape_string($plugin);
            mysql_query("DELETE FROM {$database['prefix']}Plugins WHERE owner = $owner AND name = '$plugin'");
        }
    }
    unset($xmls);
    unset($plugin);
}
function fireEvent($event,$target=null,$mother=null,$condition=true){
    global $service,$eventMappings,$pluginURL;
    if(!$condition)
        return $target;
    if(!isset($eventMappings[$event]))
        return $target;
    foreach($eventMappings[$event] as $mapping){
        include_once ("../plugins/{$mapping['plugin']}/index.php");
        if(function_exists($mapping['listener'])){
            $pluginURL="{$service['path']}/plugins/{$mapping['plugin']}";
            $target=call_user_func($mapping['listener'],$target,$mother);
        }
    }
    return $target;
}
function handleTags(&$content){
    global $service,$tagMappings,$pluginURL;
    if(preg_match_all('/\[##_(\w+)_##\]/',$content,$matches)){
        foreach($matches[1] as $tag){
            if(!isset($tagMappings[$tag]))
                continue;
            $target='';
            foreach($tagMappings[$tag] as $mapping){
                include_once ("../plugins/{$mapping['plugin']}/index.php");
                if(function_exists($mapping['handler'])){
                    $pluginURL="{$service['path']}/plugins/{$mapping['plugin']}";
                    $target=call_user_func($mapping['handler'],$target);
                }
            }
            dress($tag,$target,$content);
        }
    }
}

위의 부분인데요. 보시면 아시겠지만 간단히 설명드리겠습니다. smile

foreach($activePlugins as $plugin){
...
}

{$database['prefix']}Plugins 테이블에서 현재 사용중인 플러그인의 목록을 가져와서 루프를 돌게되죠.

설명을 위해 BlogIcon 플러그인과 TattertoolsBirthday 플러그인을 짬뽕시킨 예제;
...
<plugin version="1.0">
...
<binding>
    <listener event="ViewCommenter">BlogIcon_main</listener>
    <tag name="TattertoolsBirthday" handler="TattertoolsBirthday_TattertoolsBirthday" />
</binding>
...
</plugin>

$plugin에는 해당 플러그인의 디렉토리 이름이 있고 그 플러그인의 index.xml을 파싱을 하여 /plugin/binding/listener element와 /plugin/binding/tag element에 대하여 처리를 합니다.

listener쪽은 이벤트를 담당하는 부분으로 $eventMappings 배열에 이벤트명을 배열 index로 해서 플러그인 디렉토리명과 해당 이벤트를 처리해줄 함수명을 담습니다.
(위의 예제에서 ViewCommenter가 이벤트명이고 BlogIcon_main이 이벤트를 처리해줄 함수명입니다.)

tag쪽은 일반 치환자를 담당하는 부분으로 $tagMappings 배열에 tag element의 name attribute를 index로 해서 플러그인 디렉토리명과 해당 치환자를 처리해줄 함수명을 담습니다.
(위의 예제에서 TattertoolsBirthday가 배열 index가 되고 TattertoolsBirthday_TattertoolsBirthday가 치환자를 처리해줄 함수명입니다.)


이렇게 $eventMappings과 $tagMappings을 만들어두고 이벤트와 치환자를 각각 처리합니다.

fireEvent(($isComment?'ViewCommenter':'ViewGuestCommenter'),htmlspecialchars($commentSubItem['name']),$commentSubItem)

이벤트의 경우는 위의 예처럼 각 이벤트가 발생할 때마다 $eventMappings['ViewCommenter']에 있는 플러그인들을 include하여 call_user_func로 담아둔 함수를 호출하며 $target에 htmlspecialchars($commentSubItem['name']), $mother에 $commentSubItem를 넘겨줍니다.
따라서 이벤트로 발생하는 함수는 $target과 $mother를 둘다 받으셔야 합니다.

index.php
function BlogIcon_main($target, $mother) {
    ...
    return $target;
}

치환자의 경우는 skin을 파싱하는 부분에서 처리를 하게되는데..

    $sval=replaceSkinTag($sval,'html');
    $sval=replaceSkinTag($sval,'head');
    $sval=replaceSkinTag($sval,'body');
    handleTags($sval);

이런 부분이 있는데 위의 세줄은 [##_SKIN_html_start_##], [##_SKIN_html_end_##], [##_SKIN_head_start_##], [##_SKIN_head_end_##], [##_SKIN_body_start_##], [##_SKIN_body_end_##]의 총 6개의 자동 생성 치환자를 만드는 부분이고..
handleTags에서 위와 같은 치환자들을 처리해주는데 방식은 이벤트와 큰 차이가 없지만 $mother가 없다는 것이 다릅니다.

index.php
function TattertoolsBirthday_TattertoolsBirthday($target) {
    ...
    return $target;
}

또, 두가지의 경우 모두 $target을 받아서 반드시 return을 해주셔야 합니다.
이 것은 같은 이벤트나 치환자를 사용하는 다른 플러그인들에서도 충돌이 없이 작동을 하기 위해서인데 위의 코드를 보시면 이해가 가실겁니다. smile


ps. 힘들다;;; 근데 이해를 하실려나 OTL

김중태님의 동의가 먼저 필요하지 않을까 싶네요. smile

210

(4 답글들, 잡담하기에 작성)

아 김중태님이 만드신거였군요. smile

211

(4 답글들, 잡담하기에 작성)

http://www.yagne.com/

메인페이지 디자인은 좀 그렇기는 합니다만;;


태터툴즈 1.0.X용 플러그인이라고 합니다. 제작자분께서 태터홈에는 등록을 안하신 것 같아 링크 smile

예전에(최근에도 본거 같기는 합니다만..) iframe인가 ActiveX인가로 되어있던 것하고 비슷하네요.
(찾아볼려고했더니 뭐였는지 기억이.. OTL)

212

(11 답글들, 잡담하기에 작성)

공교롭게도(저랑은 상관없습니다만; ) WP 모임하고 같은 날이네요.;
8.19일도 블로그데이?(그러고보니 Blog하고 비슷한 것도 같고;; )

http://www.wordpress.co.kr/forum/viewtopic.php?t=369

213

(6 답글들, 잡담하기에 작성)

gofeel 작성:
daybreaker 작성:

일단은 HanIRC에 #tattertools라는 채널이 있기는 합니다만 거의 아무도 들어오지 않으셔서...

그러고보니 제가 python으로 SSL 지원하는 IRC 서버 만든다고 하다가 안드로메다로 보내버렸군요..

사람이 좀 늘면 빵글이(HanIRC에서 채널 관리를 위해 주는 봇입니다)를 신청해보는 것도 좋겠죠.

역시-_-)b

날뷁(-_-)님은 아실줄 알았습니다(...)

음 근데 기술적인 내용은 분리하는 게 좋을까요?

첫 오프모임때 이미 아침놀님께서 만들기로 했었다는 전설이 있죠.;

214

(5 답글들, 잡담하기에 작성)

구글맵은 국내의 경우 특정 지역을 제외하면 대부분 자세히 나오지 않아서 서비스로 쓰기는 무리일거 같네요. sad
제가 국내 지도 open api가 생기길 바랬던 이유이기도 하고요.

215

(5 답글들, 잡담하기에 작성)

네이버에서 지도 open(?) api를 시작했네요.

http://openapi.naver.com/19.html


약관을 읽어보니 상업 용도로 사용해도 된다는군요.(네이버하고 제휴를 해야된다고 함)
그러나 1일 5만회의 제한이 있어서 실제로 상업 용도로 사용하기는 무리겠군요.(이동, 확대/축소도 전부 포함된 5만회라니..)


근데!! 등록이 안되네요. -┏

1은 파일명이 중복되는 경우 등의 문제로 찬성하지 않습니다만 2는 괜찮은 것 같네요. smile

현재 blog/plugin을 이용하는 방법에 스킨 파서를 약간 수정해서 추가해주면 스킨과 동일한 레이아웃을 유지할 수 있을거라 생각합니다.

대충 생각해보자면.. <s_article_rep></s_article_rep>의 내부 html을 모두 새로 작성할 수 있게 해주면 될 것 같습니다.

blog/page 정도를 추가하는게 좋을까요?

218

(16 답글들, 잡담하기에 작성)

그럼 조금 늦었지만 나니님 먼저 신고식하고 시작하죠. tongue

무한의가락 작성:

음...저는 프로그래밍 같은 거 하나도 모르는 초짜인데요....
쓰다보니까 이런 것들이 구현되었으면 좋겠다고 생각해서 몇 자 씁니다.

1. 새 글쓰기를 창으로 띄워서 쓸 수 있게 하기.
이제 수정도 창에서 할 수 있게 되었는데, 글 쓰는 것도 창으로 띄워서 쓸 수 있는 모드가 있었으면 좋겠어요..

2. 관리화면 수정
관리화면이 좀 어색한 느낌이 없지 않네요. 조금 더 깔끔하게 할 수 있거나, 아니면 관리자 화면까지 스킨 적용을 할 수 있게 했으면 좋겠어요;; (너무 많이 바라나;;)

3. 원격 블로깅
오프라인과 온라인을 이어주는 원격 블로깅이 지원되었으면 하네요. 컴퓨터로 로그인하지 않고 글을 쓰고, 그것을 간단한 절차로 올릴 수 있는 프로그램 같은 것이 있었으면 좋겠어요. 물론 새로운 댓글 같은 것도 알려줄 수 있으면 더욱 좋을 것 같아요.

너무 무리한 부탁이었나요;;
아무튼 이런 부분이 다음에는 추가되었으면 좋겠어요.

1. 글쎄 필요할까 모르겠네요.

2. 1.1버전에 포함되어 있습니다.

3. 말씀해주신 것은 http://forum.tattertools.com/ko/viewtopic.php?id=785 여기를 보시면 될것 같습니다.
모블로그는 플러그인으로 구현되어있습니다.

220

(8 답글들, 잡담하기에 작성)

한글은 그냥 "태터앤프렌즈"로 붙여서 쓰는게 보기 좋지 않을까요?

221

(10 답글들, 잡담하기에 작성)

inureyes 작성:
나니 작성:

<? 찾아서 <?php로 모두 바꾸시오~ 해서 컴퓨터가 알아서 소스 수정해주면 참 좋겠네 (...........)

사실 그렇게 간단하게는 안됩니다. smile 이미 일부의 소스에 <?php가 있는 상황에서 그렇게 패치 돌리면 <?phpphp가 생긴다거나, 출력을 위한 <?xml 부분은 <?phpxml이 된다거나 하는 식으로 치환되어 버리죠. 그리고 <?= 등은 <?php echo 가 되어야 하는데 그냥 돌리면 <?php= 가 되어버리는 등등의 일이 일어납니다^^

php 정규표현식이라면 대략 아래처럼하면 말씀하신 문제는 해결이 되겠죠. smile

$pattern[] = '/(<\?)\s+/';
$pattern[] = '/<\?=/';
$replacement[] : '$1php';
$replacement[] : '<?php echo ';

다만 프로그램내에 $str = '<? blah blah'; 이런게 있다면 대략 낭패;

유마 작성:

태터스토리에서. 알림판 플러그인 설정하고 나니, 아주~~~~~~~~~~~~~~~~~~ 잘 보입니다.
다만, 알림판을 조금 더 정리 해야 될듯.. 뭐라 그럴까... 그냥 메모장에 글 써놓은 듯한;; 다닥다닥 붙은 느낌;;
정리 되지 못하고 그냥 표현되고 있는 느낌이 듭니다.. 깔끔하지 못하게~!!
태터의 기본이 바로 깔끔~ 아니겠습니까? +_+a

inureyes님과 graphittie님께서 깔끔하게 만들어 주실 겁니다. big_smile

223

(10 답글들, 잡담하기에 작성)

조만간(?) 엎어야죠.
저희 규칙아시죠?

gofeel님 화이팅!!

고생(?)하셨습니다.
오늘은 푹 쉬시고 내일부터 열혈 코딩!(...)

html tag였군요. OTL;;
(tag cloud의 tag라고 생각했; )