T2Editor Ver 3.0.2 (그누보드5 간편 에디터) (그누보드5 플러그인) > 자료실 (zip)

T2Editor Ver 3.0.2 (그누보드5 간편 에디터) (그누보드5 플러그인)

본문



2411329061739256726775_0.png



T2Editor


T2Editor는 그누보드5(Gnuboard5)를 위해 설계된 WYSIWYG 웹 에디터입니다. 이 에디터는 사용자 친화적인 인터페이스와 함께 강력한 텍스트 편집 기능을 제공하며, 특히 모바일 환경에서의 사용성을 고려하여 개발되었습니다.




작동화면:



6955575781742739253616_0.webp



주요 기능


직관적인 사용자 인터페이스 - 누구나 쉽게 사용할 수 있도록 설계된 UI 제공

모바일 환경 최적화 및 높은 호환성 - 안드로이드, iOS를 비롯한 다양한 기기에서 원활한 사용 가능

미디어 미리보기 - 삽입한 이미지 및 동영상 미리보기 지원

이미지 및 동영상 삽입/편집

- 이미지 추가(파일/링크) 및 동영상(유튜브) 삽입/업로드 가능

- 이미지 드레그&드롭 지원 및 멀티 업로드 지원

- 간편 이미지 크기 조절 메뉴 제공

코드 블록 지원 - 각종 프로그래밍 코드 입력 가능

텍스트 링크 기능 - 원하는 텍스트에 하이퍼링크 추가 가능

글꼴 크기 및 색상조절 - 손쉬운 폰트 크기 및 색상 변경

실행 취소/다시 실행 지원 - 작업 중 실수해도 쉽게 복구 가능

자동 저장 기능 - 입력한 내용이 자동으로 저장되어 데이터 손실 방지

자동 글자 수 측정 - 작성한 글자의 개수를 실시간으로 확인 가능

게시물 내보내기 기능 - 작성한 게시물을 html 파일 형태로 내보내기 가능

모듈화 된 구조 - 모듈화된 구조로 일반 사용자도 쉽게 유지보수 가능!


T2Editor ver3.0.2 수정 사항:

코드블럭이 추가되지 않는 문제를 재수정함.


변경사항 주의!

(정정)그누보드5의 게시글 html 필터링으로 인해 본문에 자동으로 추가하는 스타일 파일 링크가 작동하지 않으므로

<link href="<?php echo G5_PLUGIN_URL ?>/editor/t2editor/css/content.css" rel="stylesheet">를 추가해주시길 바랍니다. (1.6.3 -> 3.0.2버전에서 css파일이 t2content.css에서 content.css로 변경됨)

미디어블럭 스타일을 위해

head.sub.php 또는 view.skin.php에 <link href="<?php echo G5_PLUGIN_URL ?>/editor/t2editor/css/t2content.css" rel="stylesheet"> 추가했던 것을 삭제하셔도 됩니다.

(게시글에 자동으로 스타일 파일 link됨)


다운로드

게시글 하단의 첨부파일에서 다운로드하세요.



라이선스

T2Editor은 그누보드5의 발전을 위하여 코드를 공개합니다.

아래의 사항만 지킨다면 누구나 자유롭게 배포할 수 있습니다.

1. 개인(또는 사업자 자체) 사용을 위한 코드 수정 허용

2. 자체 웹사이트 사용을 위한 수정 허용

3. 수정 버전의 배포/공개 시 무료 오픈소스로 배포 필수

3. 원본 및 수정버전의 상업적 유료 배포 불가



설치방법


그누보드5 환경

1. /plugin/editor/ 디렉토리에 첨부파일의 압축을 풀어 (압축 해제한)폴더 안의 t2editor을 업로드 합니다.

2. 관리자 페이지 - 환경설정 > 기본환경설정으로 이동, 에디터 선택 항목에서 t2editor를 선택합니다. 또는 관리자 페이지 - 게시판관리 > 게시판관리 에서 원하는 게시판의 수정 버튼을 눌러 게시판 수정 페이지의 게시판 에디터 선택 항목에서 t2editor를 선택합니다.

3. 미디어블럭 스타일을 위해

head.sub.php 또는 view.skin.php에 <link href="<?php echo G5_PLUGIN_URL ?>/editor/t2editor/css/content.css" rel="stylesheet">를 추가해주세요

T2Editor Ver7.1 업데이트 이후 추가 필요 없음 (자동으로 게시글에 스타일이 link됨) ...(그누보드5 html 필터링 시스템으로 소용 없어짐)


다른 플랫폼 환경

직접 폼 제출 관련 코드를 구현해야 합니다.


<?php

include 't2_config.php';

?>


<link href="./css/core.css" rel="stylesheet">

<link href="./css/dark.css" rel="stylesheet" id="t2editor-dark-css">


<!-- Material Icons (Android 환경 고려) -->

<script>

if (navigator.userAgent.includes('Android')) {

document.write('<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">');

document.write('<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">');

}

</script>


<script>

// T2Editor 설정

const T2EDITOR_URL = './';

const t2editor_url = T2EDITOR_URL;

</script>


<!-- 다크모드 초기화 스크립트 -->

<script>

(function() {

// 다크모드 설정 옵션

const T2EditorConfig = {

enableDarkModeButton: true, // 다크모드 버튼 활성화 여부

forcedTheme: null // 강제 테마 설정 (null: 강제 설정 없음, 'light': 라이트모드 강제, 'dark': 다크모드 강제)

};


if (!T2EditorConfig.enableDarkModeButton && T2EditorConfig.forcedTheme) {

document.documentElement.setAttribute('data-t2editor-theme', T2EditorConfig.forcedTheme);

localStorage.setItem('t2editor-dark-mode', T2EditorConfig.forcedTheme === 'dark');

} else {

var isDarkMode = localStorage.getItem('t2editor-dark-mode') === 'true';


if (isDarkMode === null) {

isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;

}


if (isDarkMode) {

document.documentElement.setAttribute('data-t2editor-theme', 'dark');

} else {

document.documentElement.setAttribute('data-t2editor-theme', 'light');

}

}


document.addEventListener('DOMContentLoaded', function() {

var darkModeToggle = document.querySelector('.t2-dark-mode-toggle');

if (darkModeToggle) {

darkModeToggle.style.display = T2EditorConfig.enableDarkModeButton ? 'flex' : 'none';

}

});

})();

</script>


<!-- Material Icons 폰트 로딩 -->

<style>

@font-face {

font-family: "Material Icons";

font-style: normal;

font-weight: 400;

src: url("./fonts/material-icons/MaterialIcons-Regular.eot");

src: local("Material Icons"),

url("./fonts/material-icons/MaterialIcons-Regular.woff2") format("woff2"),

url("./fonts/material-icons/MaterialIcons-Regular.woff") format("woff"),

url("./fonts/material-icons/MaterialIcons-Regular.ttf") format("truetype");

font-display: swap;

}

.material-icons {

font-family: "Material Icons";

font-weight: normal;

font-style: normal;

font-size: 24px;

display: inline-block;

line-height: 1;

text-transform: none;

letter-spacing: normal;

word-wrap: normal;

white-space: nowrap;

direction: ltr;

-webkit-font-feature-settings: "liga";

font-feature-settings: "liga";

-webkit-font-smoothing: antialiased;

text-rendering: optimizeLegibility;

-moz-osx-font-smoothing: grayscale;

}


@font-face {

font-family: "Material Icons Outlined";

font-style: normal;

font-weight: 400;

src: url("./fonts/material-icons/MaterialIconsOutlined-Regular.woff2") format("woff2"),

url("./fonts/material-icons/MaterialIconsOutlined-Regular.ttf") format("truetype");

font-display: swap;

}


.material-icons-outlined {

font-family: "Material Icons Outlined";

font-weight: normal;

font-style: normal;

font-size: 24px;

line-height: 1;

letter-spacing: normal;

text-transform: none;

display: inline-block;

white-space: nowrap;

word-wrap: normal;

direction: ltr;

-webkit-font-feature-settings: "liga";

font-feature-settings: "liga";

-webkit-font-smoothing: antialiased;

text-rendering: optimizeLegibility;

}

</style>


<div class="t2-editor-container" id="content_container">

<div class="t2-toolbar">

<button class="t2-btn" data-command="undo" disabled>

<span class="material-icons">undo</span>

</button>

<button class="t2-btn" data-command="redo" disabled>

<span class="material-icons">redo</span>

</button>

<button class="t2-btn" data-command="fontSize">

<span class="material-icons">format_size</span>

</button>

<button class="t2-btn" data-command="bold">

<span class="material-icons">format_bold</span>

</button>

<button class="t2-btn" data-command="italic">

<span class="material-icons">format_italic</span>

</button>

<button class="t2-btn" data-command="underline">

<span class="material-icons">format_underlined</span>

</button>

<button class="t2-btn" data-command="strikeThrough">

<span class="material-icons">format_strikethrough</span>

</button>

<button class="t2-btn" data-command="justifyContent">

<span class="material-icons">format_align_left</span>

</button>

<button class="t2-btn" data-command="foreColor">

<span class="material-icons">format_color_text</span>

</button>

<button class="t2-btn" data-command="backColor">

<span class="material-icons">format_color_fill</span>

</button>

<button class="t2-btn" data-command="insertImage">

<span class="material-icons">image</span>

</button>

<button class="t2-btn" data-command="insertYouTube" style="color:#f04f48">

<span class="material-icons">smart_display</span>

</button>

<button class="t2-btn" data-command="attachFile">

<span class="material-icons">attach_file</span>

</button>

<button class="t2-btn" data-command="createLink">

<span class="material-icons">link</span>

</button>

<button class="t2-btn" data-command="insertTable">

<span class="material-icons-outlined">table_chart</span>

</button>

<button class="t2-btn" data-command="insertCodeBlock">

<span class="material-icons">code</span>

</button>

<button class="t2-btn" data-command="exportHTML">

<span class="material-icons-outlined">ios_share</span>

</button>

</div>

<div class="t2-editor" contenteditable="true" id="content_editor"></div>

<textarea name="content" id="content" style="display:none;"></textarea>

<div class="t2-editor-status">

<div class="t2-status-left">

<div class="t2-logo">

<span class="t2-logo-prefix">T2</span>

<span class="t2-logo-suffix">Editor</span>

</div>

</div>

<!-- 다크모드 토글 -->

<div class="t2-dark-mode-toggle">

<button type="button" class="t2-dark-mode-btn" onclick="toggleT2EditorTheme(event)">

<span class="material-icons t2-dark-mode-icon">dark_mode</span>

<span class="material-icons t2-light-mode-icon">light_mode</span>

</button>

</div>

<div class="t2-char-count">

txt: <span>0</span>

</div>

</div>

#999; position: absolute; right: 5px; margin:5px 0; font-size: 11px; font-weight: 500; display: flex; align-items: center;">

<i class="material-icons-outlined" style="margin-right: 4px; font-size: 14px">info</i>

T2Editor Ver 7.1

</span>

</div>


<!-- T2Editor JavaScript -->

<script src="./js/utils.js"></script>

<script src="./js/core.js"></script>


<!-- T2Editor 플러그인 -->

<script src="./js/plugin/image.js"></script>

<script src="./js/plugin/video.js"></script>

<script src="./js/plugin/file.js"></script>

<script src="./js/plugin/table.js"></script>

<script src="./js/plugin/code.js"></script>

<script src="./js/plugin/link.js"></script>

<script src="./js/plugin/export.js"></script>


<script>

// 다크모드 토글 함수

function toggleT2EditorTheme(event) {

if (event) {

event.preventDefault();

event.stopPropagation();

}

const currentTheme = document.documentElement.getAttribute('data-t2editor-theme');

const newTheme = currentTheme === 'dark' ? 'light' : 'dark';


document.documentElement.setAttribute('data-t2editor-theme', newTheme);

localStorage.setItem('t2editor-dark-mode', newTheme === 'dark');

}


// 에디터 초기화

(function() {

const editor = new T2Editor(document.getElementById('content_container'));

window.content_editor = editor;


// 초기 컨텐츠가 있다면 로드

const initialContent = document.getElementById('content').value;

if (initialContent) {

try {

var tempDiv = document.createElement('div');

tempDiv.innerHTML = initialContent;


// 중복 줄바꿈 제거

var nodes = Array.from(tempDiv.childNodes);

for (let i = nodes.length - 1; i >= 0; i--) {

let current = nodes[i];

if (current.nodeType === Node.ELEMENT_NODE && current.tagName === 'P' && !current.textContent.trim() && current.querySelector('br')) {

let prev = i > 0 ? nodes[i - 1] : null;

let next = i < nodes.length - 1 ? nodes[i + 1] : null;


if ((prev && prev.classList?.contains('t2-media-block')) ||

(next && next.classList?.contains('t2-media-block'))) {

current.remove();

}

}

}


// 기존 table-responsive 구조를 t2-table-wrapper로 변환

tempDiv.querySelectorAll('.table-responsive').forEach(function(responsiveWrapper) {

var table = responsiveWrapper.querySelector('table');

if (table) {

if (!table.classList.contains('t2-table')) {

table.classList.add('t2-table');

}


var isLargeTable = table.classList.contains('t2-table-large') ||

(table.rows.length > 10 || (table.rows[0] && table.rows[0].cells.length > 10));

if (isLargeTable && !table.classList.contains('t2-table-large')) {

table.classList.add('t2-table-large');

}


var tableWrapper = document.createElement('div');

tableWrapper.className = 't2-table-wrapper';

tableWrapper.contentEditable = false;


responsiveWrapper.parentNode.insertBefore(tableWrapper, responsiveWrapper);


if (isLargeTable) {

var scrollWrapper = document.createElement('div');

scrollWrapper.className = 't2-table-scroll-wrapper';

tableWrapper.appendChild(scrollWrapper);

scrollWrapper.appendChild(table);

} else {

tableWrapper.appendChild(table);

}


responsiveWrapper.remove();

}

});


editor.setContent(tempDiv.innerHTML);


// 초기화 완료 후 처리

setTimeout(function() {

editor.normalizeContent();


// 플러그인들에게 컨텐츠 로드 완료 알림

for (let [name, plugin] of editor.plugins) {

if (plugin.onContentSet) {

plugin.onContentSet(tempDiv.innerHTML);

}

}

}, 100);

} catch (e) {

console.error('에디터 초기화 오류:', e);

}

}

})();


// 폼 제출시 컨텐츠 처리 함수

function getEditorContent() {

var editorContent = document.getElementById('content_editor').innerHTML;

var tempDiv = document.createElement('div');

tempDiv.innerHTML = editorContent;


// 파일 블록 처리

tempDiv.querySelectorAll('.t2-file-block').forEach(function(block) {

const controls = block.querySelector('.t2-media-controls');

if (controls) {

controls.remove();

}

});


// 미디어 블록 처리

tempDiv.querySelectorAll('.t2-media-block').forEach(function(block) {

var container = block.querySelector('div:first-child');

var mediaElement = container.querySelector('iframe, video, img');

if (mediaElement) {

mediaElement.style.width = container.style.width;

if (container.style.height) {

mediaElement.style.height = container.style.height;

}


var controls = block.querySelector('.t2-media-controls');

if (controls) {

controls.remove();

}

}

});


// 테이블 래퍼 및 컨트롤 처리

tempDiv.querySelectorAll('.t2-table-wrapper').forEach(function(wrapper) {

const table = wrapper.querySelector('table');

if (table) {

const controls = wrapper.querySelector('.t2-table-controls');

if (controls) {

controls.remove();

}


const downloadBtn = wrapper.querySelector('.t2-table-download-btn');

if (downloadBtn) {

downloadBtn.remove();

}


const isLargeTable = table.classList.contains('t2-table-large') ||

(table.rows.length > 10 || (table.rows[0] && table.rows[0].cells.length > 10));


const hasScrollWrapper = wrapper.querySelector('.t2-table-scroll-wrapper');


if (isLargeTable || hasScrollWrapper) {

const scrollContainer = document.createElement('div');

scrollContainer.className = 'table-responsive';

scrollContainer.style.cssText = 'display:block; width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch;';


wrapper.parentNode.insertBefore(scrollContainer, wrapper);


if (hasScrollWrapper) {

hasScrollWrapper.parentNode.insertBefore(table, hasScrollWrapper);

hasScrollWrapper.remove();

}


scrollContainer.appendChild(table);

wrapper.remove();

} else {

wrapper.parentNode.insertBefore(table, wrapper);

wrapper.remove();

}

}

});


// 코드 블록 처리

tempDiv.querySelectorAll('.t2-code-block').forEach(function(block) {

const toolbar = block.querySelector('.t2-code-toolbar');

if (toolbar) {

toolbar.remove();

}

});


// 빈 문단 처리

tempDiv.querySelectorAll('p').forEach(function(p) {

if (!p.textContent.trim() && !p.querySelector('img, iframe, video')) {

if (!p.querySelector('br')) {

p.innerHTML = '<br>';

}

}

});


// 컨텐츠 스타일 추가

var contentStyle = '<link href="./css/content.css" rel="stylesheet">';


var finalContent = tempDiv.innerHTML;


if (finalContent.indexOf('t2-media-block') !== -1 || finalContent.indexOf('t2-table') !== -1 || finalContent.indexOf('t2-code-block') !== -1) {

finalContent += contentStyle;

}


document.getElementById('content').value = finalContent;

return finalContent;

}


// 컨텐츠 검증 함수

function validateEditorContent() {

var editorContent = document.getElementById('content_editor').innerHTML;


function hasRealContent(html) {

var tempDiv = document.createElement('div');

tempDiv.innerHTML = html;


var textContent = tempDiv.textContent.trim();

if (textContent) return true;


if (tempDiv.querySelector('img, video, iframe')) return true;

if (tempDiv.querySelector('.t2-file-block, .file-container')) return true;

if (tempDiv.querySelector('table')) return true;


return false;

}


if (!hasRealContent(editorContent)) {

alert('내용을 입력해 주십시오.');

document.getElementById('content_editor').focus();

return false;

}

return true;

}

</script>




필수 환경


PHP 7.x ~

GD Livrary




기여

펄스나인(false9)님의 IOS/Safari에서 발생하는 IME, 엔터키 문제 해결 방법 제시.

jihan006(jihan006)님의 IOS의 IME 문제 해결 관련 자료 제공.

푸른산타(bodr)님의 관리자 에러 해결


*이 게시물은 T2Editor로 작성되었습니다.


좋아요28 이 글을 좋아요하셨습니다
url 복사 카카오톡 공유 라인 공유 페이스북 공유 트위터 공유

첨부파일

카테고리 분류 학습 시스템 (총 0개 학습됨)

예측 카테고리: 경제-금융 (랜덤 - 학습 데이터 없음)

이 분류가 맞나요? 학습시켜주세요!

등록된 댓글이 없습니다.

  • _  글쓰기 글쓰기
전체 89건
게시물 검색

접속자집계

오늘
1,666
어제
3,897
최대
42,418
전체
939,894