ken changes
This commit is contained in:
parent
478e6e8ec9
commit
e6c7b932aa
|
@ -67,6 +67,32 @@ export class JsmindSearch {
|
||||||
// Ensure input event is not bound multiple times
|
// Ensure input event is not bound multiple times
|
||||||
inputField.removeEventListener('input', this.onInput)
|
inputField.removeEventListener('input', this.onInput)
|
||||||
inputField.addEventListener('input', this.onInput.bind(this, node))
|
inputField.addEventListener('input', this.onInput.bind(this, node))
|
||||||
|
|
||||||
|
inputField.removeEventListener('keydown', this.onKeyDown)
|
||||||
|
inputField.addEventListener('keydown', this.onKeyDown.bind(this, node))
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 處理 Enter 鍵完成輸入
|
||||||
|
* Handle Enter key to finalize input
|
||||||
|
* @param {Object} node - 當前節點
|
||||||
|
* @param {KeyboardEvent} e - 鍵盤事件
|
||||||
|
*/
|
||||||
|
onKeyDown(node, e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault()
|
||||||
|
const input = e.target.value.trim()
|
||||||
|
if (input) {
|
||||||
|
// 更新節點文字
|
||||||
|
node.data.text = input
|
||||||
|
this.jm.end_edit()
|
||||||
|
this.jm.update_node(node.id, input)
|
||||||
|
|
||||||
|
// 隱藏 suggestion box(避免未選建議但仍留下)
|
||||||
|
if (this.suggestionBox) {
|
||||||
|
this.suggestionBox.style.display = 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,10 +132,22 @@ export class JsmindSearch {
|
||||||
// 更新建議框內容
|
// 更新建議框內容
|
||||||
// Update suggestion box content
|
// Update suggestion box content
|
||||||
this.suggestionBox.innerHTML = results
|
this.suggestionBox.innerHTML = results
|
||||||
.map(
|
.map(item => {
|
||||||
(item) =>
|
const fieldHtml = item.fields.map(f => {
|
||||||
`<div class="${SUGGESTION_ITEM_CLASS}" data-link="${item.link}" data-text="${item.text}">${item.text}</div>`
|
const txt = f.url
|
||||||
)
|
? `<a href="${f.url}" target="_blank">${f.text}</a>`
|
||||||
|
: f.text;
|
||||||
|
return `<div class="field-row"><strong>${f.title}:</strong> ${txt}</div>`;
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="suggestion-item" data-link="${item.link}">
|
||||||
|
${fieldHtml}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
.join('')
|
.join('')
|
||||||
|
|
||||||
this.suggestionBox.style.left = `${left}px`
|
this.suggestionBox.style.left = `${left}px`
|
||||||
|
@ -145,20 +183,72 @@ export class JsmindSearch {
|
||||||
* @param {Object} node - 當前選中節點 (Selected node)
|
* @param {Object} node - 當前選中節點 (Selected node)
|
||||||
* @param {Event} e - 點擊事件 (Click event)
|
* @param {Event} e - 點擊事件 (Click event)
|
||||||
*/
|
*/
|
||||||
|
// onSuggestionClick(node, e) {
|
||||||
|
// e.preventDefault()
|
||||||
|
|
||||||
|
// const text = e.target.getAttribute('data-text')
|
||||||
|
// const link = e.target.getAttribute('data-link')
|
||||||
|
|
||||||
|
// node.data.text = text
|
||||||
|
// node.data.link = link
|
||||||
|
|
||||||
|
// this.jm.end_edit()
|
||||||
|
// this.jm.update_node(node.id, text)
|
||||||
|
|
||||||
|
// // 選擇後隱藏建議框
|
||||||
|
// // Hide suggestions after selection
|
||||||
|
// this.suggestionBox.style.display = 'none'
|
||||||
|
// }
|
||||||
onSuggestionClick(node, e) {
|
onSuggestionClick(node, e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const text = e.target.getAttribute('data-text')
|
const item = e.currentTarget // 確保抓到整個 .suggestion-item DIV
|
||||||
const link = e.target.getAttribute('data-link')
|
const html = item.innerHTML // 取得完整 HTML 當作 topic
|
||||||
|
|
||||||
node.data.text = text
|
node.data.text = html
|
||||||
node.data.link = link
|
node.data.link = item.getAttribute('data-link')
|
||||||
|
|
||||||
this.jm.end_edit()
|
this.jm.end_edit()
|
||||||
this.jm.update_node(node.id, text)
|
this.jm.update_node(node.id, html)
|
||||||
|
|
||||||
// 選擇後隱藏建議框
|
|
||||||
// Hide suggestions after selection
|
|
||||||
this.suggestionBox.style.display = 'none'
|
this.suggestionBox.style.display = 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// ✅ 新增播放語音事件委派,支援動態插入的 voice-player
|
||||||
|
let audio;
|
||||||
|
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const target = e.target.closest('.voice-player');
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let status = target.getAttribute('status');
|
||||||
|
if (audio) {
|
||||||
|
audio.pause();
|
||||||
|
audio.currentTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 'playing') {
|
||||||
|
target.setAttribute('status', '');
|
||||||
|
const icon = target.querySelector('i');
|
||||||
|
icon?.classList.remove('fa-pause');
|
||||||
|
icon?.classList.add('fa-play');
|
||||||
|
} else {
|
||||||
|
let mp3_url = target.getAttribute('data-content');
|
||||||
|
audio = new Audio(mp3_url);
|
||||||
|
audio.play();
|
||||||
|
|
||||||
|
target.setAttribute('status', 'playing');
|
||||||
|
const icon = target.querySelector('i');
|
||||||
|
icon?.classList.remove('fa-play');
|
||||||
|
icon?.classList.add('fa-pause');
|
||||||
|
|
||||||
|
audio.onended = function() {
|
||||||
|
target.setAttribute('status', '');
|
||||||
|
icon?.classList.remove('fa-pause');
|
||||||
|
icon?.classList.add('fa-play');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
|
@ -79,7 +79,7 @@ jmnode {
|
||||||
color: #333;
|
color: #333;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
box-shadow: 1px 1px 1px #666;
|
box-shadow: 1px 1px 1px #666;
|
||||||
font: 1.4em/1.125 Verdana, Arial, Helvetica, sans-serif;
|
font: 1em/1.125 Verdana, Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
jmnode:hover {
|
jmnode:hover {
|
||||||
|
@ -96,8 +96,7 @@ jmnode.selected {
|
||||||
}
|
}
|
||||||
|
|
||||||
jmnode.root {
|
jmnode.root {
|
||||||
font-size:1.6em;
|
font-size:1.2em;
|
||||||
font-size:2em;
|
|
||||||
/* 展開/收合按鈕 */
|
/* 展開/收合按鈕 */
|
||||||
border-color: gray;
|
border-color: gray;
|
||||||
}
|
}
|
||||||
|
@ -113,12 +112,12 @@ jmexpander:hover {
|
||||||
jmnode {
|
jmnode {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-size: 1.6em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
jmnode.root {
|
jmnode.root {
|
||||||
/* font-size: 21px; */
|
/* font-size: 21px; */
|
||||||
font-size: 1.8em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +152,7 @@ jmexpander:hover {
|
||||||
============================ */
|
============================ */
|
||||||
.jsmind-suggestions {
|
.jsmind-suggestions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 200px;
|
height: fit-content;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
@ -170,3 +169,24 @@ jmexpander:hover {
|
||||||
.suggestion-item:hover {
|
.suggestion-item:hover {
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
.field-row{
|
||||||
|
.column_entry_files{
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: unset;
|
||||||
|
}
|
||||||
|
strong{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:nth-child(2){
|
||||||
|
a{
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
&:nth-child(4){
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<div class="entry-suggestion">
|
||||||
|
<strong><%= entry.column_entries.first.try(:text).to_s %></strong>
|
||||||
|
|
||||||
|
<% entry.column_entries.each do |ce| %>
|
||||||
|
<% if ce.type == "file" %>
|
||||||
|
<ul class="column_entry_files">
|
||||||
|
<% ce.column_entry_files.desc(:sort_number).each do |file| %>
|
||||||
|
<% next unless file.choose_lang_display(I18n.locale.to_s) %>
|
||||||
|
<% file_title = file.get_file_title %>
|
||||||
|
<% size = number_to_human_size(file.file.size) %>
|
||||||
|
<% link = file.get_link %>
|
||||||
|
<% if file.file.content_type.start_with?('audio/') %>
|
||||||
|
<div class="voice-player">
|
||||||
|
<span class="voice-title"><%= file_title %></span>
|
||||||
|
<a class="voice-player" data-content="<%= file.file.url %>" href="#" title="<%= file_title %>">
|
||||||
|
<i class="fa fa-play" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<li class="column_entry_file">
|
||||||
|
<a class="column_entry_file_link" href="<%= link %>" title="<%= file_title %>" target="_blank">
|
||||||
|
<%= file_title %>
|
||||||
|
</a>
|
||||||
|
<span class="file_size">(<%= size %>)</span>
|
||||||
|
<span class="view_count">
|
||||||
|
<i class="fa fa-eye" title="<%= t("universal_table.downloaded_times") %>"></i>
|
||||||
|
<span class="view-count"><%= file.download_count %></span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
|
@ -48,31 +48,53 @@ class Admin::UniversalTablesController < OrbitAdminController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_entries
|
def get_entries
|
||||||
table = UTable.where(:uid => params["uid"]).first rescue nil
|
table = UTable.where(:uid => params["uid"]).first rescue nil
|
||||||
data = []
|
data = []
|
||||||
if !table.nil?
|
|
||||||
if params[:q].present?
|
if table && params[:q].present?
|
||||||
enteries = search_data(table, 50)
|
entries = search_data(table, 50)
|
||||||
ma = ModuleApp.find_by_key("universal_table")
|
ma = ModuleApp.find_by_key("universal_table")
|
||||||
enteries.each do |entry|
|
|
||||||
if params["links"].present?
|
entries.each do |entry|
|
||||||
data << {
|
rows = []
|
||||||
"id" => entry.id.to_s,
|
entry.column_entries.each do |ce|
|
||||||
"text" => entry.column_entries.first.text,
|
ct = ce.table_column
|
||||||
"link" => OrbitHelper.cal_url_to_show(ma,entry)
|
next if ct.nil?
|
||||||
}
|
next if ct.display_in_index == false
|
||||||
else
|
|
||||||
data << {
|
text = ce.get_frontend_text(ct)
|
||||||
"id" => entry.id.to_s,
|
next if text.blank?
|
||||||
"text" => entry.column_entries.first.text
|
|
||||||
}
|
# 包含連結的欄位處理
|
||||||
end
|
url = ct.is_link_to_show ? OrbitHelper.cal_url_to_show(ma, entry) : nil
|
||||||
end
|
rows << {
|
||||||
end
|
"title" => ct.title,
|
||||||
end
|
"text" => text,
|
||||||
render :json => data.to_json
|
"url" => url
|
||||||
end
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# 加入 hashtags 欄位
|
||||||
|
# rows << {
|
||||||
|
# "title" => I18n.t("universal_table.hashtags"),
|
||||||
|
# "text" => entry.tags_for_frontend,
|
||||||
|
# "url" => nil
|
||||||
|
# }
|
||||||
|
|
||||||
|
# 加入主輸出結構
|
||||||
|
data << {
|
||||||
|
"id" => entry.id.to_s,
|
||||||
|
"link" => OrbitHelper.cal_url_to_show(ma, entry),
|
||||||
|
"text" => entry.column_entries.map(&:text).compact.reject(&:blank?).join(" / "),
|
||||||
|
"fields" => rows
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: data
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def get_mindmaps
|
def get_mindmaps
|
||||||
utable = UTable.where(:uid => params['table']).first
|
utable = UTable.where(:uid => params['table']).first
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<%
|
<%
|
||||||
data = action_data
|
data = action_data
|
||||||
OrbitHelper.render_css_in_head(["/mind_map/mindmap"])
|
OrbitHelper.render_css_in_head(["mind_map/mindmap"])
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<h2><%= data["title"] %></h2>
|
<h2><%= data["title"] %></h2>
|
||||||
|
@ -31,6 +31,7 @@
|
||||||
// Custom options for the mind map (refer to the jsmind official documentation)
|
// Custom options for the mind map (refer to the jsmind official documentation)
|
||||||
const options = {
|
const options = {
|
||||||
container: 'jsmind_container',
|
container: 'jsmind_container',
|
||||||
|
support_html: true,
|
||||||
editable: isEditable,
|
editable: isEditable,
|
||||||
theme: 'primary',
|
theme: 'primary',
|
||||||
mode: 'full',
|
mode: 'full',
|
||||||
|
|
Loading…
Reference in New Issue