@@ -59,7 +59,7 @@
< body >
< body >
< a class = "review__skip" href = "#results" > Перейти к результатам< / a >
< a class = "review__skip" href = "#results" > Перейти к результатам< / a >
< header class = "review__header" > < h1 > Оценка поисковой выдачи< / h1 > < p > Проверяйте результаты нашего OpenSearch по практическим юридическим запросам.< / p > < / header >
< header class = "review__header" > < h1 > Оценка поисковой выдачи< / h1 > < p > Проверяйте результаты нашего OpenSearch по практическим юридическим запросам.< / p > < / header >
< main class = "review__main" >
< main class = "review__main" id = "review" >
< form class = "review__search" id = "search-form" >
< form class = "review__search" id = "search-form" >
< label class = "review__field" > Запрос< input id = "query" name = "q" maxlength = "500" required autocomplete = "off" > < / label >
< label class = "review__field" > Запрос< input id = "query" name = "q" maxlength = "500" required autocomplete = "off" > < / label >
< label class = "review__field review__field--small" > Язык< select id = "language" name = "language" > < option value = "ru" > Русский< / option > < option value = "ky" > Кыргызский< / option > < / select > < / label >
< label class = "review__field review__field--small" > Язык< select id = "language" name = "language" > < option value = "ru" > Русский< / option > < option value = "ky" > Кыргызский< / option > < / select > < / label >
@@ -75,13 +75,17 @@
< div class = "review__actions" > < label > Проверяющий < input id = "reviewer" maxlength = "120" autocomplete = "name" > < / label > < label > Общий комментарий < input id = "overall-comment" maxlength = "4000" > < / label > < button class = "review__button--primary" id = "save" type = "button" > Сохранить оценку< / button > < / div >
< div class = "review__actions" > < label > Проверяющий < input id = "reviewer" maxlength = "120" autocomplete = "name" > < / label > < label > Общий комментарий < input id = "overall-comment" maxlength = "4000" > < / label > < button class = "review__button--primary" id = "save" type = "button" > Сохранить оценку< / button > < / div >
< dialog id = "document-dialog" > < button class = "review__dialog-close" id = "dialog-close" type = "button" > Закрыть< / button > < h2 id = "dialog-heading" > Документ< / h2 > < div class = "review__document-body" id = "dialog-body" > < / div > < / dialog >
< dialog id = "document-dialog" > < button class = "review__dialog-close" id = "dialog-close" type = "button" > Закрыть< / button > < h2 id = "dialog-heading" > Документ< / h2 > < div class = "review__document-body" id = "dialog-body" > < / div > < / dialog >
< div class = "review__status review__status--error" id = "error" role = "alert" aria-live = "assertive" > < / div >
< div class = "review__status review__status--error" id = "error" role = "alert" aria-live = "assertive" > < / div >
< footer class = "review__header" > Акылдаш · Backend v0.8.0 · Внутренняя лаборатория релевантности< / footer >
< footer class = "review__header" > Акылдаш · Backend v0.8.1 · Внутренняя лаборатория релевантности< / footer >
< script >
< script >
const state = { results : [ ] , token : '' , selected : null } ;
const DRAFT _KEY = 'akyldash-search-review-draft' ;
const state = { results : [ ] , token : '' , selected : null , query : '' } ;
const $ = ( id ) => document . getElementById ( id ) ;
const $ = ( id ) => document . getElementById ( id ) ;
const setStatus = ( text ) => { $ ( 'status' ) . textContent = text ; $ ( 'error' ) . textContent = '' ; } ;
const setStatus = ( text ) => { $ ( 'status' ) . textContent = text ; $ ( 'error' ) . textContent = '' ; } ;
const setError = ( text ) => { $ ( 'error' ) . textContent = text ; } ;
const setError = ( text ) => { $ ( 'error' ) . textContent = text ; } ;
const escapeText = ( value ) => value == null ? '' : String ( value ) ;
const escapeText = ( value ) => value == null ? '' : String ( value ) ;
const readDraft = ( ) => { try { return JSON . parse ( localStorage . getItem ( DRAFT _KEY ) || 'null' ) ; } catch ( _ ) { return null ; } } ;
const saveDraft = ( ) => { if ( ! state . token ) return ; const results = [ ... $ ( 'results' ) . children ] . map ( ( item , index ) => ( { rank : index + 1 , code : item . dataset . code , rating : item . querySelector ( 'input:checked' ) ? . value ? ? null , comment : item . querySelector ( 'textarea' ) . value } ) ) ; try { localStorage . setItem ( DRAFT _KEY , JSON . stringify ( { query : state . query , language : $ ( 'language' ) . value , pageSize : $ ( 'page-size' ) . value , reviewer : $ ( 'reviewer' ) . value , overallComment : $ ( 'overall-comment' ) . value , results } ) ) ; } catch ( _ ) { } } ;
const applyDraft = ( ) => { const draft = readDraft ( ) ; if ( ! draft || draft . query !== state . query || draft . language !== $ ( 'language' ) . value || draft . pageSize !== $ ( 'page-size' ) . value ) return ; $ ( 'reviewer' ) . value = draft . reviewer || '' ; $ ( 'overall-comment' ) . value = draft . overallComment || '' ; ( draft . results || [ ] ) . forEach ( ( saved ) => { const item = [ ... $ ( 'results' ) . children ] . find ( ( candidate ) => candidate . dataset . rank === String ( saved . rank ) && candidate . dataset . code === saved . code ) ; if ( ! item ) return ; if ( saved . rating != null ) { const input = item . querySelector ( ` input[value=" ${ CSS . escape ( String ( saved . rating ) ) } "] ` ) ; if ( input ) input . checked = true ; } item . querySelector ( 'textarea' ) . value = saved . comment || '' ; } ) ; } ;
function renderResults ( ) {
function renderResults ( ) {
$ ( 'results' ) . replaceChildren ( ) ;
$ ( 'results' ) . replaceChildren ( ) ;
state . results . forEach ( ( result , index ) => {
state . results . forEach ( ( result , index ) => {
@@ -94,14 +98,17 @@
const comment = document . createElement ( 'textarea' ) ; comment . className = 'review__comment' ; comment . maxLength = 4000 ; comment . placeholder = 'Комментарий к результату (необязательно)' ; comment . setAttribute ( 'aria-label' , ` Комментарий к результату # ${ index + 1 } ` ) ;
const comment = document . createElement ( 'textarea' ) ; comment . className = 'review__comment' ; comment . maxLength = 4000 ; comment . placeholder = 'Комментарий к результату (необязательно)' ; comment . setAttribute ( 'aria-label' , ` Комментарий к результату # ${ index + 1 } ` ) ;
item . append ( title , meta , snippet , rating , comment ) ; $ ( 'results' ) . append ( item ) ;
item . append ( title , meta , snippet , rating , comment ) ; $ ( 'results' ) . append ( item ) ;
} ) ;
} ) ;
applyDraft ( ) ;
}
}
async function openDocument ( index , trigger ) {
async function openDocument ( index , trigger ) {
const result = state . results [ index ] ; state . selected = trigger ; setStatus ( 'Загрузка документа…' ) ;
const result = state . results [ index ] ; state . selected = trigger ; setStatus ( 'Загрузка документа…' ) ;
try { const response = await fetch ( ` /documents/ ${ encodeURIComponent ( result . code ) } /editions/ ${ encodeURIComponent ( result . edition ) } ?language= ${ encodeURIComponent ( $ ( 'language' ) . value ) } ` ) ; if ( ! response . ok ) throw new Error ( 'Документ недоступен' ) ; const payload = await response . json ( ) ; const content = payload . content [ $ ( 'language' ) . value ] ; const meta = $ ( 'document-meta' ) ; meta . textContent = [ result . name , result . status , result . date _adopted ] . filter ( Boolean ) . join ( ' · ' ) ; const source = document . createElement ( 'a' ) ; source . href = ` https://cbd.minjust.gov.kg/ ${ encodeURIComponent ( result . code ) } /edition/ ${ encodeURIComponent ( result . edition ) } / ${ encodeURIComponent ( $ ( 'language' ) . value ) } ` ; source . target = '_blank' ; source . rel = 'noreferrer' ; source . textContent = ' Официальный источник' ; meta . append ( source ) ; $ ( 'document-body' ) . innerHTML = content . html ; $ ( 'dialog-heading' ) . textContent = result . name || 'Документ' ; $ ( 'dialog-body' ) . innerHTML = content . html ; if ( matchMedia ( '(max-width: 800px)' ) . matches ) $ ( 'document-dialog' ) . showModal ( ) ; setStatus ( 'Документ загружен.' ) ; } catch ( error ) { setError ( 'Н е удалось загрузить документ. Повторите попытку.' ) ; }
try { const response = await fetch ( ` /documents/ ${ encodeURIComponent ( result . code ) } /editions/ ${ encodeURIComponent ( result . edition ) } ?language= ${ encodeURIComponent ( $ ( 'language' ) . value ) } ` ) ; if ( ! response . ok ) throw new Error ( 'Документ недоступен' ) ; const payload = await response . json ( ) ; const content = payload . content [ $ ( 'language' ) . value ] ; const meta = $ ( 'document-meta' ) ; meta . textContent = [ result . name , result . status , result . date _adopted ] . filter ( Boolean ) . join ( ' · ' ) ; const source = document . createElement ( 'a' ) ; source . href = ` https://cbd.minjust.gov.kg/ ${ encodeURIComponent ( result . code ) } /edition/ ${ encodeURIComponent ( result . edition ) } / ${ encodeURIComponent ( $ ( 'language' ) . value ) } ` ; source . target = '_blank' ; source . rel = 'noreferrer' ; source . textContent = ' Официальный источник' ; meta . append ( source ) ; $ ( 'document-body' ) . innerHTML = content . html ; $ ( 'dialog-heading' ) . textContent = result . name || 'Документ' ; $ ( 'dialog-body' ) . innerHTML = content . html ; if ( matchMedia ( '(max-width: 800px)' ) . matches ) $ ( 'document-dialog' ) . showModal ( ) ; setStatus ( 'Документ загружен.' ) ; } catch ( error ) { setError ( 'Н е удалось загрузить документ. Повторите попытку.' ) ; }
}
}
$ ( 'dialog-close' ) . addEventListener ( 'click' , ( ) => { $ ( 'document-dialog' ) . close ( ) ; if ( state . selected ) state . selected . focus ( ) ; } ) ;
$ ( 'dialog-close' ) . addEventListener ( 'click' , ( ) => { $ ( 'document-dialog' ) . close ( ) ; if ( state . selected ) state . selected . focus ( ) ; } ) ;
$ ( 'search-form' ) . addEventListener ( 'submit' , async ( event ) => { event . preventDefault ( ) ; const query = $ ( 'query' ) . value . trim ( ) ; if ( ! query ) return ; setStatus ( 'Поиск выполняется…' ) ; $ ( 'save' ) . disabled = true ; try { const params = new URLSearchParams ( { q : query , language : $ ( 'language' ) . value , page _size : $ ( 'page-size' ) . value } ) ; const response = await fetch ( ` /search? ${ params } ` ) ; if ( ! response . ok ) throw new Error ( ) ; const payload = await response . json ( ) ; state . results = payload . results ; state . token = payload . review _token ; renderResults ( ) ; setStatus ( state . results . length ? ` Найдено результатов: ${ state . results . length } . ` : ` По запросу « ${ query } » ничего не найдено. Измените запрос. ` ) ; } catch ( error ) { state . results = [ ] ; state . token = '' ; renderResults ( ) ; setError ( 'Н е удалось выполнить поиск. Повторите поиск.' ) ; } finally { $ ( 'save' ) . disabled = false ; } } ) ;
$ ( 'search-form' ) . addEventListener ( 'submit' , async ( event ) => { event . preventDefault ( ) ; const query = $ ( 'query' ) . value . trim ( ) ; if ( ! query ) return ; state . query = query ; setStatus ( 'Поиск выполняется…' ) ; $ ( 'save' ) . disabled = true ; try { const params = new URLSearchParams ( { q : query , language : $ ( 'language' ) . value , page _size : $ ( 'page-size' ) . value } ) ; const response = await fetch ( ` /search? ${ params } ` ) ; if ( ! response . ok ) throw new Error ( ) ; const payload = await response . json ( ) ; state . results = payload . results ; state . token = payload . review _token ; renderResults ( ) ; setStatus ( state . results . length ? ` Найдено результатов: ${ state . results . length } . ` : ` По запросу « ${ query } » ничего не найдено. Измените запрос. ` ) ; } catch ( error ) { state . results = [ ] ; state . token = '' ; renderResults ( ) ; setError ( 'Н е удалось выполнить поиск. Повторите поиск.' ) ; } finally { $ ( 'save' ) . disabled = false ; } } ) ;
$ ( 'save' ) . addEventListener ( 'click ' , async ( ) => { if ( ! state . token ) { setError ( 'Сначала выполните поиск.' ) ; return ; } const reviewer = $ ( 'reviewer' ) . value . trim ( ) ; if ( ! reviewer ) { $ ( 'reviewer' ) . focus ( ) ; setError ( 'Укажите проверяющего.' ) ; return ; } const results = [ ... $ ( 'results' ) . children ] . map ( ( item , index ) => ( { rank : index + 1 , code : item . dataset . code , rating : item . querySelector ( 'input:checked' ) ? Number ( item . querySelector ( 'input:checked' ) . value ) : null , comment : item . querySelector ( 'textarea' ) . value } ) ) ; $ ( 'save' ) . disabled = true ; setStatus ( 'Сохранение выполняется…' ) ; try { const response = await fetch ( '/search-reviews' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify ( { review _token : state . token , reviewer , overall _comment : $ ( 'overall-comment' ) . value , results } ) } ) ; if ( ! response . ok ) throw new Error ( ) ; const payload = await response . json ( ) ; setStatus ( ` Оценка сохранена · № ${ payload . id } . ` ) ; } catch ( error ) { setError ( 'Н е удалось сохранить. Проверьте подключение и повторите.' ) ; } finally { $ ( 'save' ) . disabled = false ; } } ) ;
document . addEventListener ( 'input ' , saveDraft ) ; document . addEventListener ( 'change' , saveDraft ) ;
$ ( 'save' ) . addEventListener ( 'click' , async ( ) => { if ( ! state . token ) { setError ( 'Сначала выполните поиск.' ) ; return ; } const reviewer = $ ( 'reviewer' ) . value . trim ( ) ; if ( ! reviewer ) { $ ( 'reviewer' ) . focus ( ) ; setError ( 'Укажите проверяющего.' ) ; return ; } const results = [ ... $ ( 'results' ) . children ] . map ( ( item , index ) => ( { rank : index + 1 , code : item . dataset . code , rating : item . querySelector ( 'input:checked' ) ? Number ( item . querySelector ( 'input:checked' ) . value ) : null , comment : item . querySelector ( 'textarea' ) . value } ) ) ; $ ( 'save' ) . disabled = true ; setStatus ( 'Сохранение выполняется…' ) ; try { const response = await fetch ( '/search-reviews' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify ( { review _token : state . token , reviewer , overall _comment : $ ( 'overall-comment' ) . value , results } ) } ) ; if ( ! response . ok ) throw new Error ( ) ; const payload = await response . json ( ) ; localStorage . removeItem ( DRAFT _KEY ) ; setStatus ( ` Оценка сохранена · № ${ payload . id } . ` ) ; } catch ( error ) { setError ( 'Н е удалось сохранить. Проверьте подключение и повторите.' ) ; } finally { $ ( 'save' ) . disabled = false ; } } ) ;
const draft = readDraft ( ) ; if ( draft ) { $ ( 'query' ) . value = draft . query || '' ; $ ( 'language' ) . value = draft . language || 'ru' ; $ ( 'page-size' ) . value = draft . pageSize || '20' ; $ ( 'reviewer' ) . value = draft . reviewer || '' ; $ ( 'overall-comment' ) . value = draft . overallComment || '' ; }
< / script >
< / script >
< / body >
< / body >
< / html >
< / html >