PageSourceSearch

https://e-nrp.org/js/forthcomingAdd.js

js e-nrp.org collected 2026-10-03 23:52:18 UTC 11,778 bytes, 298 lines download raw bytes

1async function initForthcoming(articleList) {
2    const container = document.querySelector('.forthcoming .content-row');
3    if (!container || !articleList) return;
4	
5	// 1. Skeleton UI 미리 생성 (리스트 개수만큼)
6    articleList.forEach((item, index) => {
7        if (item.articleType) {
8            const subHeadingHtml = `<h6 class="AOPSubheading">${item.articleType}</h6>`;
9            container.insertAdjacentHTML('beforeend', subHeadingHtml);
10        } else {
11	        const skeletonHtml = `
12	            <div class="articleBox card border-0" id="article-slot-${index}">
13	                <div class="row gx-3 gy-3">
14	                    <div class="col-md-12">
15	                        <div class="skeleton skeleton-img"></div>
16	                    </div>
17	                    <div class="col-md-12">
18	                        <div class="content">
19	                            <div class="skeleton skeleton-text" style="width:30%"></div>
20	                            <div class="skeleton skeleton-title"></div>
21	                            <div class="skeleton skeleton-text"></div>
22	                            <div class="skeleton skeleton-text" style="width:50%"></div>
23	                        </div>
24	                    </div>
25	                </div>
26	            </div>
27	        `;
28	        container.insertAdjacentHTML('beforeend', skeletonHtml);
29	    }
30	});
31
32    // 정렬용 빈 박스 미리 삽입 (2개)
33    const preEmptyHtml = `
34        <div class="articleBox card border-0 empty-box" style="visibility:hidden; height:0; margin:0!important; padding:0;"></div>
35        <div class="articleBox card border-0 empty-box" style="visibility:hidden; height:0; margin:0!important; padding:0;"></div>`;
36    container.insertAdjacentHTML('beforeend', preEmptyHtml);
37
38
39    // 2. 실행 전략 분할
40	const PRIORITY_COUNT = 3; // 먼저 처리할 개수
41	const priorityItems = articleList.slice(0, PRIORITY_COUNT);
42	const backgroundItems = articleList.slice(PRIORITY_COUNT);
43
44	/**
45	 * A. 상위 3편: 병렬 처리 (즉시 렌더링)
46	 */
47	const priorityPromises = priorityItems.map(async (item, index) => {
48	    if (!item.doi) return null;
49	    try {
50	        const data = await fetchForthcomingArticleData(item.doi, item.im, item.sub);
51	        updateArticleSlot(index, data); // 완료되는 대로 즉시 화면 업데이트
52	    } catch (err) {
53	        updateArticleSlot(index, { error: true, doi: item.doi });
54	    }
55	});
56
57
58	// 우선 순위 작업이 모두 시작됨 (완료를 기다리지 않고 백그라운드 루프 예약 가능)
59	// 하지만 초기 화면의 안정성을 위해 우선 순위 완료를 기다린 후 나머지를 돌리는 것이 좋습니다.
60	await Promise.all(priorityPromises);
61
62	/**
63	 * B. 나머지: 순차 처리 (하나씩 차례대로)
64	 * for...of 문을 사용하면 await가 걸릴 때마다 다음 루프로 넘어가기 전에 대기합니다.
65	 */
66	for (let i = 0; i < backgroundItems.length; i++) {
67	    const item = backgroundItems[i];
68	    const originalIndex = i + PRIORITY_COUNT; // 전체 리스트에서의 인덱스 계산
69
70	    if (!item.doi) continue;
71
72	    try {
73	        // 이전 요청이 끝나야 다음 요청이 시작됨 (서버 부하 방지 및 순서 보장)
74	        const data = await fetchForthcomingArticleData(item.doi, item.im, item.sub);
75	        updateArticleSlot(originalIndex, data);
76	    } catch (err) {
77	        updateArticleSlot(originalIndex, { error: true, doi: item.doi });
78	    }
79	}
80
81}
82
83// 데이터만 가져오는 순수 파싱 함수 (UI 조작 제외)
84async function fetchForthcomingArticleData(doi, imInput, subsubject) {
85	const journalID = '0161NRP';
86	const journalAcron = 'nrp'
87
88	const urlsToTry = [
89        `https://e-nrp.org/DOIx.php?id=${doi}`,
90        `https://www.e-nrp.org/DOIx.php?id=${doi}`
91    ];
92
93    let response;
94    let finalUrl = "";
95
96    // 2. 성공할 때까지 순차적으로 시도합니다.
97    for (const url of urlsToTry) {
98        try {
99            const res = await fetch(url);
100            if (res.ok) {
101                response = res;
102                finalUrl = url;
103                break; // 성공하면 루프 종료
104            }
105        } catch (err) {
106            console.warn(`${url} 접속 실패, 다음 주소 시도 중...`);
107        }
108    }
109
110    if (!response) {
111        console.error("모든 URL 접속에 실패했습니다.");
112        return;
113    }
114
115	try {
116	    const htmlText = await response.text();
117	    const parser = new DOMParser();
118	    const doc = parser.parseFromString(htmlText, "text/html");
119
120	    // 기본 데이터 할당
121	    let frontMeta = doc.querySelector("#article-front-meta-left")?.innerText || "";
122	    let vol = "", page = "", epub = "";
123	    
124	    if (frontMeta.includes('.')) {
125	        vol = frontMeta.split('.')[1].trim().split(';')[1].split(':')[0];
126	        page = frontMeta.split('.')[1].trim().split(':')[1].split('-')[0];
127	    	epub = frontMeta.split('.')[4].trim()
128	    }
129
130	    let im = imInput;
131	    let imgDisplay = "";
132	    if (!im) {
133	    	// 1. Graphical Abstract 체크
134			const headers = Array.from(doc.querySelectorAll('.article-section-header h2'));
135			const hasGraphicalAbstract = headers.some(h => h.innerText.toLowerCase().includes('graphical abstract'));
136
137			if (hasGraphicalAbstract) {
138			    im = "abf001";
139			} else {
140			    // 2. Figures 체크
141			    const portletHeaders = Array.from(doc.querySelectorAll('.porlet h5'));
142			    const hasFigures = portletHeaders.some(h => h.innerText.toLowerCase().includes('figures'));
143			    const hasTables = portletHeaders.some(h => h.innerText.toLowerCase().includes('tables'));
144
145		        if (hasFigures) {
146		            im = "g001-l";
147		        } else if (hasTables) {
148		            im = "i001-l";
149		        } else {
150		            // 둘 다 없을 경우
151		            im = "xxxx";
152		            imgDisplay = " d-none"; // 부트스트랩 클래스 또는 CSS 클래스
153		        }
154			}
155	    }
156
157		const citeButton = doc.querySelector('.CiteDialog');
158		let author = "";
159		const metaJournal = doc.querySelector('meta[name="citation_journal_title"]')?.getAttribute('content')?.trim();
160
161
162	    // 1. data 속성에서 암호화된 텍스트 가져오기
163	    const encodedCitation = citeButton.getAttribute('data-article-citation');
164	    
165	    if (encodedCitation) {
166	        try {
167	            // 2. Base64 디코딩 (atob 함수 사용)
168	            // 인코딩된 문자열 내부에 HTML 태그나 특수문자가 섞여 있을 수 있으므로 처리
169	            const decodedText = decodeURIComponent(escape(window.atob(encodedCitation)));
170	            
171	            // 3. 디코딩된 텍스트에서 첫 번째 마침표(.)까지 자르기
172	            author = decodedText.split('.')[0].trim() + ".";
173
174		        // 4. 추출된 텍스트 안에 학술지명이 포함되어 있다면 이탤릭체로 치환
175		        if (author.includes(metaJournal)) {
176		            // 해당 학술지명 부분만 <i>태그로 감싸기
177		            author = author.replace(metaJournal, `<i>${metaJournal}</i>`);
178		        }
179
180	        } catch (e) {
181	            console.error("Base64 디코딩 실패:", e);
182	            // 디코딩 실패 시 대안으로 메타데이터 사용
183	            const metaAuthor = doc.querySelector('meta[name="citation_author"]')?.content;
184	            author = metaAuthor ? metaAuthor + "." : "Author Unknown.";
185	        }
186	    }
187
188		let subject = doc.querySelector('.article-type')?.innerText;
189
190		// 1. 페이지 내 모든 <script> 태그를 검사
191		const scripts = Array.from(doc.querySelectorAll('script'));
192		const typeScript = scripts.find(s => s.textContent.includes('document.getElementsByClassName("article-type")[0].innerHTML'));
193
194		if (typeScript) {
195		    // 정규표현식으로 "Research Article" 부분만 추출
196		    // .innerHTML = "..." 또는 .innerHTML = '...' 형태를 모두 찾음
197		    const match = typeScript.textContent.match(/\.innerHTML\s*=\s*["'](.+?)["']/);
198		    if (match && match[1]) {
199		        subject = match[1];
200		    }
201		}
202
203		// 본문 형태에 따라 abs만 연결한 논문인지 확인
204		const bodyElement = doc.querySelector('#article-level-0-body');
205		let isAbsOnly = false;
206
207		if (!bodyElement) {
208		  isAbsOnly = true;
209		} else {
210		  const children = bodyElement.children;
211		  const hasOnlySupplementary = (children.length === 1 && children[0].id === 'supplementary-material');
212		  
213		  const textContent = bodyElement.innerText.trim();
214		  if (hasOnlySupplementary || textContent.length < 50) { 
215		      isAbsOnly = true;
216		  }
217		}
218
219		let displayLink = `https://doi.org/${doi}`;
220		let displayText = displayLink;
221
222		if (isAbsOnly) {
223		 epub = epub.replace('Published ', 'Posted ');
224		 displayLink = finalUrl;
225		 displayText = 'Abstract';
226		}
227
228
229	    // 예시용 데이터 객체 반환
230	    return {
231	    	imgDisplay: imgDisplay,
232	        imgSrc: `../ArticleImage/${journalID}/${journalAcron}-${vol}-${page}-${im}.jpg` || '',
233	        subject: subject,
234	        subsubject: subsubject,
235	        title: doc.querySelector('.article-title .content-title')?.innerHTML || '',
236	        author: author,
237	        history: doc.querySelector('.article-received-accepted-date')?.innerText || '',
238	        epub: epub,
239	        link: finalUrl,
240	        doi: doi,
241	        page: page,
242	        displayLink: displayLink,
243	        displayText: displayText
244		};
245    } catch (e) {
246        return { title: "Error loading data", doi: doi };
247    }
248}
249
250function updateArticleSlot(index, data) {
251    const slot = document.getElementById(`article-slot-${index}`);
252    if (!slot) return;
253
254    let articleHtml = "";
255
256    if (data.error) {
257        // [로드 실패 시 UI]
258        articleHtml = `
259            <div class="row gx-3 gy-3 error-box">
260                <div class="col-12 text-center py-4">
261                    <div class="error-icon" style="font-size: 24px;">⚠️</div>
262                    <div class="error-text" style="color: #888; font-size: 13px; margin-top: 10px;">
263                        Failed to load data.<br>
264                        <small><a href="https://doi.org/${data.doi}" target="_blank">${data.doi}</a></small>
265                    </div>
266                </div>
267            </div>
268        `;
269    } else {
270        // [정상 로드 시 UI]
271    	articleHtml = `
272	        <div class="row gx-3 gy-3">
273	            <div class="col-md-12${data.imgDisplay}">
274	                <div class="thumbnail"><img class="img-fluid myImg" src="${data.imgSrc}"/></div>
275	            </div>
276	            <div class="col-md-12">
277	                <div class="content">
278	                    <div class="d-flex justify-content-between">
279	                        <div class="subject">${data.subject}</div>
280	                        <div class="sub_articleType">Forthcoming</div>
281	                    </div>
282	                    <div class="articleType"></div>
283	                    <div class="card-text title"><a href="${data.link}">${data.title}</a></div>
284	                    <div class="card-text author">${data.author}</div>
285	                    <div class="card-text disc">
286	                        <div class="history">${data.history}</div>
287	                        <div class="epub">${data.epub}</div>
288	                        <div class="doi"><a href="${data.link}">Abstract + Full Text</a></div>
289	                    </div>
290	                </div>
291	            </div>
292	        </div>
293    	`;
294    }
295    
296    slot.innerHTML = articleHtml;
297    slot.classList.add('loaded');
298}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.