Ediphy 정리
0. 용어
1) toolbar : 박스마다 pluginToolbarsById에 저장되는 값. 각 플러그인마다 특수하게 필요한 값은 보통 여기 저장된다. 꾸미기 관련된 값도 여기 저장된다.
2) toolbar.state(state) : 박스 클릭 시 오른쪽 툴바에서 보이는 값들은 여기에 저장된다.
3) box : boxesById에 저장되는 값. 주로 element간 상하관계나 위치값을 참조.
4) view toolbar : 박스말고 슬라이드 자체에 관한 값을 저장.
1. 플러그인을 추가하려면
1) /plugins/[PluginName]/... 에 플러그인을 작성
2) core/config.es6, config_noserver.es6, config_production.es6의 pluginList에 PluginName을 추가
2. DevTools
개발모드에서는 DevTools를 쓸 수 있다. 발생한 액션을 차례대로 볼 수 있다.
단 DevTools를 쓰면 같은 액션이 ReduxProvider와 DevTools로 2번 들어가므로 개발모드에서 됐던 기능이 배포모드에서는 안될 수도 있고 그 반대일 수도 있다.
3. dispatch()
dispatch()는 EditorApp.jsx에만 props로 들어가 있는 것 같다.
child component로 보내주면 쓸 수 있는지는 아직 테스트하지 않음.
dispatch 자체를 보내기 보단 dispatch를 쓰는 함수를 EditorApp에서 만들어서 그 함수를 보내는 방식인 것 같다.
4. 페이지 순서를 가져오려면
navItemsIds가 아니라 navItemsById[0].children을 참조해야 한다.
navItemsIds는 만들어진 순서대로 저장되기 때문에 페이지 순서를 바꾸면 어긋난다.
5. EditorBox
표(BasicTable)나 빈 객체(EmptyObject) 안에 있는 플러그인이라면 EditorCanvasSli > EditorBox(표나 빈 객체) > PluginPlaceholder > Editorbox,
안에 있지 않은 플러그인이라면 EditorCanvasSli > Editorbox이다.
EditorBox에 상속하고 싶은게 있다면 두 군데 추가해야 한다.
6. 플러그인을 정의하는 js 파일
여기에 플러그인이 정의되지만 react component는 아니므로 react component처럼 쓸 수는 없다.
getRenderTemplate()가 state, props를 인자로 받으니 이걸 활용해야 한다.
7. boxes_by_id.es6
boxReducer에서 인자로 받는 state는 toolbar의 state가 아니다. toolbar의 state는 updateBox action으로 바꿀 수 있다.
8. 텍스트 플러그인(BasicText...)
텍스트 편집기로 CKEditor4를 react component로 만들어서 사용한다(CKEDitorComponent).
toolbar.state.__text에 저장된다.
저장되는 텍스트는 순수 텍스트가 아니라 html이며 이걸 다시 encodeURI해서 저장한다.
예를 들어 "내용을 입력해주세요." -> "<p>내용을 입력해주세요.</p>" -> "%3Cp%3E%EB%82%B4%EC%9A%A9%EC%9D%84%20%EC%9E%85%EB%A0%A5%ED%95%B4%EC%A3%BC%EC%84%B8%EC%9A%94.%3C/p%3E".
9. 텍스트 플러그인의 값을 강제로 바꾸려면
toolbar state만 바꾸면 되는 줄 알았는데 아니었다.
toolbar.state.__text만 바꾸면 텍스트 편집 모드로 들어가야 바뀐 텍스트가 적용된다.
box.content도 바꿔야 한다.
10. p 태그 안의 텍스트를 복사하는 방법
Modal 안의 그냥 텍스트를 복사하려고 했는데 현재 슬라이드가 복사되는 현상이 발생했다.
Clipboard.jsx 안의 copyListenr 함수를 보면 현재 선택된 box가 없으면 현재 슬라이드를 복사하게 돼있다(clipboardData를 덮어씀).
현재 focus된 객체(activeElement)가 선택할 수 있는 텍스트를 포함하면 clipboardData를 덮어쓰지 않게 하기 위해(일반적인 이벤트) activeElement가 될 수 있는 element에 id나 class를 지정한다.
Modal 안의 텍스트를 선택하면 activeElement가 Modal로 지정되므로 Modal에 지정하도록 한다.
이후 copyListener 함수를 아래처럼 수정
...
else if (this.props.navItemSelected !== 0) {
if (!(activeElement.classList.contains('CLASS') || activeElement.id === 'ID')) {
event.preventDefault();
event.clipboardData.setData("text/plain", JSON.stringify(this.copyNavItemData()));
return true;
}
}...
11. 외부 이미지 복사 붙여넣기
Clipboard.jsx의 duplicateListener -> pasteListner의 else if (!this.containsCKEDitorText(activeElement)) 부분 참조.
클립보드 데이터를 검사해서 이미지면 uploadFunction 실행 후 HotspotImages 객체를 만듦.
uploadFunction은 EditorApp.jsx에서부터 props로 내려옴.
특별히 수정하지 않았다면 actions.es6d에 정의된 uploadEdiphyResourceAsync 함수.
12. 외부 이미지 복붙 시 파일을 서버에 올리는 이유
actions.es6의 uploadEdiphyResourceAsync 함수를 보면 굳이 서버에 올리지 않아도 되는 듯 하다(fetch 후 첫 번째 catch callback 참조).
서버에 올리지 않고 catch callback에 있는 부분만 실행해도 이미지는 잘 들어가나 base64로 변환해서 저장하기 때문에 export할 때 용량이 매우매우 커진다.
따라서 되도록 서버에 올리고 필요한 정보만 받아서 저장하는 게 낫다.
13. 틀객체/표의 내부객체를 찾아오려면
navItemsById.(페이지ID).boxes에 틀객체 혹은 표의 내부 객체들은 포함되지 않는다.
내부 객체를 찾아오려면 부모객체의 boxesById.(boxID).sortableContainers.(scID).children에서 box id를 가져와 boxesById와 pluginToolbarsById에서 찾아와야 한다.
14. 파일을 json으로 export
EditorApp.jsx에서 <EditorNavBar...의 export란 이름으로 넘겨주는 함수에 아래 코드를 추가.
else if (format === "JSON") {
let _state = this.props.store.getState();
let _obj = {
filesUploaded: _state.filesUploaded,
globalConfig: _state.undoGroup.present.globalConfig,
boxesById: _state.undoGroup.present.boxesById,
navItemsIds: _state.undoGroup.present.navItemsIds,
navItemsById: _state.undoGroup.present.navItemsById,
navItemSelected: _state.undoGroup.present.navItemsById[0].children[0],
marksById: _state.undoGroup.present.marksById,
pluginToolbarsById: _state.undoGroup.present.pluginToolbarsById,
viewToolbarsById: _state.undoGroup.present.viewToolbarsById,
exercises: _state.undoGroup.present.exercises,
};
let filename = _state.undoGroup.present.globalConfig.title.replace(/[^a-z0-9ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/gi, '_').toLowerCase();
let blob = new Blob([JSON.stringify(_obj)], { type: "text/plain;charset=utf-8" });
let link = document.createElement('a');
link.setAttribute('href', window.URL.createObjectURL(blob));
link.setAttribute('download', filename + '.json');
document.body.appendChild(link);
link.click();
callback();
}
15. json 파일을 import
14에서 export한 형식으로 다시 import.
파일을 열기 위해 file input을 만듦 -> json 파일을 선택하고 읽어옴 -> importState.
EditorApp.jsx의 render 함수에 아래 코드를 추가.
<input type="file" id="inputLoadJsonFile" style={{ display: 'none' }} accept="application/json,.json"
onChange={(event) => {
let input = event.target;
if (!confirm('정말로 파일을 덮어 씌우시겠습니까?')) {
input.value = '';
return;
}
let reader = new FileReader();
reader.onload = () => {
let _file = JSON.parse(reader.result);
let _state = { present: { ...this.props, ..._file } };
this.props.dispatch(importState(serialize(_state)));
};
reader.readAsText(input.files[0]);
input.value = '';
}}/>
16. visor에서는 index.html에서 전역으로 선언한 변수를 사용할 수 없다(정확히 확인중).
17. FileModal에 component를 추가하려면
1) accept할 타입을 정한다. 굳이 mimetype이 아니어도 된다.
2) /external_provider/file_modal/APIProviders/에 Component 추가
이 component를 써서 입력을 받게 된다.
저장할 값이 변하는 부분(onChange, onClick 등)에 props.onElementSelected로 변경 내용을 반영해야한다.
3) FileHandlers.es6의 handlers함수에 해당 타입을 처리하는 부분을 추가
4) FileModal을 여는 함수 openFileModal(BOXID, accept)의 accept 부분에 1)에서 정한 타입을 넘긴다.
18. EditorBox.jsx에서 plugin에 정의한 내용을 실제로 가져오는 부분
찾으면 금방 나오긴 하는데 render() 함수의 이 부분이다.
{toolbar.showTextEditor ? null : content }
let content = config.flavor === "react" ? (
<div style={style} {...attrs} className={"boxStyle " + classNames} ref={"content"}>
{Ediphy.Plugins.get(toolbar.pluginId).getRenderTemplate(toolbar.state, this.props)}
</div>
) : (
<div style={style} {...attrs} className={"boxStyle " + classNames} ref={"content"}>
{this.renderChildren(box.content)}
</div>
);
23. 외부 이미지 복사 시 toolbar의 state에 항목을 추가하려면
1) toolbar : 박스마다 pluginToolbarsById에 저장되는 값. 각 플러그인마다 특수하게 필요한 값은 보통 여기 저장된다. 꾸미기 관련된 값도 여기 저장된다.
2) toolbar.state(state) : 박스 클릭 시 오른쪽 툴바에서 보이는 값들은 여기에 저장된다.
3) box : boxesById에 저장되는 값. 주로 element간 상하관계나 위치값을 참조.
4) view toolbar : 박스말고 슬라이드 자체에 관한 값을 저장.
1. 플러그인을 추가하려면
1) /plugins/[PluginName]/... 에 플러그인을 작성
2) core/config.es6, config_noserver.es6, config_production.es6의 pluginList에 PluginName을 추가
2. DevTools
개발모드에서는 DevTools를 쓸 수 있다. 발생한 액션을 차례대로 볼 수 있다.
단 DevTools를 쓰면 같은 액션이 ReduxProvider와 DevTools로 2번 들어가므로 개발모드에서 됐던 기능이 배포모드에서는 안될 수도 있고 그 반대일 수도 있다.
3. dispatch()
dispatch()는 EditorApp.jsx에만 props로 들어가 있는 것 같다.
child component로 보내주면 쓸 수 있는지는 아직 테스트하지 않음.
dispatch 자체를 보내기 보단 dispatch를 쓰는 함수를 EditorApp에서 만들어서 그 함수를 보내는 방식인 것 같다.
4. 페이지 순서를 가져오려면
navItemsIds가 아니라 navItemsById[0].children을 참조해야 한다.
navItemsIds는 만들어진 순서대로 저장되기 때문에 페이지 순서를 바꾸면 어긋난다.
5. EditorBox
표(BasicTable)나 빈 객체(EmptyObject) 안에 있는 플러그인이라면 EditorCanvasSli > EditorBox(표나 빈 객체) > PluginPlaceholder > Editorbox,
안에 있지 않은 플러그인이라면 EditorCanvasSli > Editorbox이다.
EditorBox에 상속하고 싶은게 있다면 두 군데 추가해야 한다.
6. 플러그인을 정의하는 js 파일
여기에 플러그인이 정의되지만 react component는 아니므로 react component처럼 쓸 수는 없다.
getRenderTemplate()가 state, props를 인자로 받으니 이걸 활용해야 한다.
7. boxes_by_id.es6
boxReducer에서 인자로 받는 state는 toolbar의 state가 아니다. toolbar의 state는 updateBox action으로 바꿀 수 있다.
8. 텍스트 플러그인(BasicText...)
텍스트 편집기로 CKEditor4를 react component로 만들어서 사용한다(CKEDitorComponent).
toolbar.state.__text에 저장된다.
저장되는 텍스트는 순수 텍스트가 아니라 html이며 이걸 다시 encodeURI해서 저장한다.
예를 들어 "내용을 입력해주세요." -> "<p>내용을 입력해주세요.</p>" -> "%3Cp%3E%EB%82%B4%EC%9A%A9%EC%9D%84%20%EC%9E%85%EB%A0%A5%ED%95%B4%EC%A3%BC%EC%84%B8%EC%9A%94.%3C/p%3E".
9. 텍스트 플러그인의 값을 강제로 바꾸려면
toolbar state만 바꾸면 되는 줄 알았는데 아니었다.
toolbar.state.__text만 바꾸면 텍스트 편집 모드로 들어가야 바뀐 텍스트가 적용된다.
box.content도 바꿔야 한다.
10. p 태그 안의 텍스트를 복사하는 방법
Modal 안의 그냥 텍스트를 복사하려고 했는데 현재 슬라이드가 복사되는 현상이 발생했다.
Clipboard.jsx 안의 copyListenr 함수를 보면 현재 선택된 box가 없으면 현재 슬라이드를 복사하게 돼있다(clipboardData를 덮어씀).
현재 focus된 객체(activeElement)가 선택할 수 있는 텍스트를 포함하면 clipboardData를 덮어쓰지 않게 하기 위해(일반적인 이벤트) activeElement가 될 수 있는 element에 id나 class를 지정한다.
Modal 안의 텍스트를 선택하면 activeElement가 Modal로 지정되므로 Modal에 지정하도록 한다.
이후 copyListener 함수를 아래처럼 수정
...
else if (this.props.navItemSelected !== 0) {
if (!(activeElement.classList.contains('CLASS') || activeElement.id === 'ID')) {
event.preventDefault();
event.clipboardData.setData("text/plain", JSON.stringify(this.copyNavItemData()));
return true;
}
}...
11. 외부 이미지 복사 붙여넣기
Clipboard.jsx의 duplicateListener -> pasteListner의 else if (!this.containsCKEDitorText(activeElement)) 부분 참조.
클립보드 데이터를 검사해서 이미지면 uploadFunction 실행 후 HotspotImages 객체를 만듦.
uploadFunction은 EditorApp.jsx에서부터 props로 내려옴.
특별히 수정하지 않았다면 actions.es6d에 정의된 uploadEdiphyResourceAsync 함수.
12. 외부 이미지 복붙 시 파일을 서버에 올리는 이유
actions.es6의 uploadEdiphyResourceAsync 함수를 보면 굳이 서버에 올리지 않아도 되는 듯 하다(fetch 후 첫 번째 catch callback 참조).
서버에 올리지 않고 catch callback에 있는 부분만 실행해도 이미지는 잘 들어가나 base64로 변환해서 저장하기 때문에 export할 때 용량이 매우매우 커진다.
따라서 되도록 서버에 올리고 필요한 정보만 받아서 저장하는 게 낫다.
13. 틀객체/표의 내부객체를 찾아오려면
navItemsById.(페이지ID).boxes에 틀객체 혹은 표의 내부 객체들은 포함되지 않는다.
내부 객체를 찾아오려면 부모객체의 boxesById.(boxID).sortableContainers.(scID).children에서 box id를 가져와 boxesById와 pluginToolbarsById에서 찾아와야 한다.
14. 파일을 json으로 export
EditorApp.jsx에서 <EditorNavBar...의 export란 이름으로 넘겨주는 함수에 아래 코드를 추가.
else if (format === "JSON") {
let _state = this.props.store.getState();
let _obj = {
filesUploaded: _state.filesUploaded,
globalConfig: _state.undoGroup.present.globalConfig,
boxesById: _state.undoGroup.present.boxesById,
navItemsIds: _state.undoGroup.present.navItemsIds,
navItemsById: _state.undoGroup.present.navItemsById,
navItemSelected: _state.undoGroup.present.navItemsById[0].children[0],
marksById: _state.undoGroup.present.marksById,
pluginToolbarsById: _state.undoGroup.present.pluginToolbarsById,
viewToolbarsById: _state.undoGroup.present.viewToolbarsById,
exercises: _state.undoGroup.present.exercises,
};
let filename = _state.undoGroup.present.globalConfig.title.replace(/[^a-z0-9ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/gi, '_').toLowerCase();
let blob = new Blob([JSON.stringify(_obj)], { type: "text/plain;charset=utf-8" });
let link = document.createElement('a');
link.setAttribute('href', window.URL.createObjectURL(blob));
link.setAttribute('download', filename + '.json');
document.body.appendChild(link);
link.click();
callback();
}
15. json 파일을 import
14에서 export한 형식으로 다시 import.
파일을 열기 위해 file input을 만듦 -> json 파일을 선택하고 읽어옴 -> importState.
EditorApp.jsx의 render 함수에 아래 코드를 추가.
<input type="file" id="inputLoadJsonFile" style={{ display: 'none' }} accept="application/json,.json"
onChange={(event) => {
let input = event.target;
if (!confirm('정말로 파일을 덮어 씌우시겠습니까?')) {
input.value = '';
return;
}
let reader = new FileReader();
reader.onload = () => {
let _file = JSON.parse(reader.result);
let _state = { present: { ...this.props, ..._file } };
this.props.dispatch(importState(serialize(_state)));
};
reader.readAsText(input.files[0]);
input.value = '';
}}/>
16. visor에서는 index.html에서 전역으로 선언한 변수를 사용할 수 없다(정확히 확인중).
17. FileModal에 component를 추가하려면
1) accept할 타입을 정한다. 굳이 mimetype이 아니어도 된다.
2) /external_provider/file_modal/APIProviders/에 Component 추가
이 component를 써서 입력을 받게 된다.
저장할 값이 변하는 부분(onChange, onClick 등)에 props.onElementSelected로 변경 내용을 반영해야한다.
3) FileHandlers.es6의 handlers함수에 해당 타입을 처리하는 부분을 추가
4) FileModal을 여는 함수 openFileModal(BOXID, accept)의 accept 부분에 1)에서 정한 타입을 넘긴다.
18. EditorBox.jsx에서 plugin에 정의한 내용을 실제로 가져오는 부분
찾으면 금방 나오긴 하는데 render() 함수의 이 부분이다.
{toolbar.showTextEditor ? null : content }
content는 이 부분에서 정의한다.
<div style={style} {...attrs} className={"boxStyle " + classNames} ref={"content"}>
{Ediphy.Plugins.get(toolbar.pluginId).getRenderTemplate(toolbar.state, this.props)}
</div>
) : (
<div style={style} {...attrs} className={"boxStyle " + classNames} ref={"content"}>
{this.renderChildren(box.content)}
</div>
);
코드를 따로 수정하지 않으면 toolbar의 state와 EditorBox로 넘어온 props를 인자로 넘긴다.
플러그인 별로 따로 필요한 인자가 있다면 props에 얹어서 넘겨주는 방법도 있다.
19. 템플릿 경로
/_editor/components/carousel/templates_modal/templates/templates.es6
19. 템플릿 경로
/_editor/components/carousel/templates_modal/templates/templates.es6
20. 더 이상 안쓰게된 플러그인을 가리려면
/core/config...에서 그냥 삭제하면 그 플러그인을 사용한 페이지에 들어간 순간 에러가 발생한다.
더 이상 만들게 하고 싶지 않으면 plugin/[PluginName]/[PluginName].js 파일 getConfig 함수에 설정된 category를 없는 category로 바꿔주면 된다.
cateogry는 /_editor/components/nav_bar/editor_nav_bar/PluginsMenu.jsx에 정의되어있다.
21. Visor plugin의 props
Visor 플러그인은 EditorApp -> Preview -> (core/visor/main.es6(exportPage) -> dist/lib/visor/index.ejs -> visor.bundle.js ->)
VisorApp -> VisorCanvas -> VisorCanvasSli -> VisorBox를 거쳐서 렌더링되는 것 같다.
props는 EditorApp -> Preview의 render 함수에서 Ediphy.Visor.exportPage로 넘긴 state가 -> VisorApp에서 Ediphy.State로 넘어온다. VisorCanvas로 넘어가는 props는 canvasProps라는 변수이므로 이 안에 같이 넣어서 넘기면 된다.
22. 텍스트 생성 후 표 안에 넣을 때
/core/config...에서 그냥 삭제하면 그 플러그인을 사용한 페이지에 들어간 순간 에러가 발생한다.
더 이상 만들게 하고 싶지 않으면 plugin/[PluginName]/[PluginName].js 파일 getConfig 함수에 설정된 category를 없는 category로 바꿔주면 된다.
cateogry는 /_editor/components/nav_bar/editor_nav_bar/PluginsMenu.jsx에 정의되어있다.
21. Visor plugin의 props
Visor 플러그인은 EditorApp -> Preview -> (core/visor/main.es6(exportPage) -> dist/lib/visor/index.ejs -> visor.bundle.js ->)
VisorApp -> VisorCanvas -> VisorCanvasSli -> VisorBox를 거쳐서 렌더링되는 것 같다.
props는 EditorApp -> Preview의 render 함수에서 Ediphy.Visor.exportPage로 넘긴 state가 -> VisorApp에서 Ediphy.State로 넘어온다. VisorCanvas로 넘어가는 props는 canvasProps라는 변수이므로 이 안에 같이 넣어서 넘기면 된다.
22. 텍스트 생성 후 표 안에 넣을 때
/reducer/plugin_toolbars_by_id.es6의 case DROP_BOX: 아래의 코드에서 표 안에 들어갈 때 크기, 위치 등을 지정할 수 있다.
높이 기본값이 고정값인데, 이렇게 넣으면 텍스트를 여러 줄 입력해도 높이가 늘어나지 않아 불편한 점이 있다.
23. 외부 이미지 복사 시 toolbar의 state에 항목을 추가하려면
외부 이미지 복사했을 때 HotspotImages 객체가 생성되는 건 uploadFunction(Clipboard.jsx) -> createBox(common_tool.es6) -> addBox(actions.es6) -> toolbarCreator(plugin_toolbars_by_id.es6) (+ 다른 reducer) 순서이다.
생성 후에 state에 항목을 추가하는 건 잘 되는데 최초 생성 시 추가하는 건 안되서 봤더니 toolbarCreator에 지정해주어야 했다.
정확히는 createBox에 인자로 넘기는 initialParams에 항목을 추가해서 넘김 -> 추가한 항목을 toolbarCreator에서 받아서 state에 추가해야 한다.