mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	expose add link on UI, fixes #95
This commit is contained in:
		
							parent
							
								
									cb69914f09
								
							
						
					
					
						commit
						1a737f7d19
					
				
							
								
								
									
										1
									
								
								src/public/images/trilium.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/public/images/trilium.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path d="M63.966,45.043c0.008-0.009,0.021-0.021,0.027-0.029c0.938-1.156-0.823-13.453-5.063-20.125  c-1.389-2.186-2.239-3.423-3.219-4.719c-3.907-5.166-6-6.125-6-6.125S35.732,24.78,36.149,44.315  c-1.754,0.065-11.218,7.528-14.826,14.388c-1.206,2.291-1.856,3.645-2.493,5.141c-2.539,5.957-2.33,8.25-2.33,8.25  s16.271,6.79,33.014-3.294c0.007,0.021,0.013,0.046,0.02,0.063c0.537,1.389,12.08,5.979,19.976,5.621  c2.587-0.116,4.084-0.238,5.696-0.444c6.424-0.818,8.298-2.157,8.298-2.157S81.144,54.396,63.966,45.043z M50.787,65.343  c1.059-1.183,4.648-5.853,0.995-11.315c-0.253-0.377-0.496-0.236-0.496-0.236s0.063,10.822-5.162,12.359  c-5.225,1.537-13.886,4.4-20.427,0.455C25,66.186,26.924,53.606,38.544,47.229c0.546,1.599,2.836,6.854,9.292,6.409  c0.453-0.031,0.453-0.313,0.453-0.313s-9.422-5.328-8.156-10.625s3.089-14.236,9.766-17.948c0.714-0.397,10.746,7.593,10.417,20.94  c-1.606-0.319-7.377-1.004-10.226,4.864c-0.198,0.409,0.046,0.549,0.046,0.549s9.31-5.521,13.275-1.789  c3.965,3.733,10.813,9.763,10.71,17.4C74.111,67.533,62.197,72.258,50.787,65.343z M35.613,35.145c0,0-0.991,3.241-0.603,7.524  l-13.393-7.524C21.618,35.145,27.838,30.931,35.613,35.145z M21.193,36.03l13.344,7.612c-3.872,1.872-6.142,4.388-6.142,4.388  C20.78,43.531,21.193,36.03,21.193,36.03z M72.287,49.064c0,0-2.321-2.471-6.23-4.263l13.187-7.881  C79.243,36.92,79.808,44.413,72.287,49.064z M78.687,36.113l-13.237,7.794c0.3-4.291-0.754-7.511-0.754-7.511  C72.383,32.025,78.687,36.113,78.687,36.113z M42.076,73.778c0,0,3.309-0.737,6.845-3.185l0.056,15.361  C48.977,85.955,42.244,82.621,42.076,73.778z M49.956,85.888L50,70.526c3.539,2.445,6.846,3.181,6.846,3.181  C56.686,82.551,49.956,85.888,49.956,85.888z"></path></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.8 KiB  | 
@ -2,7 +2,8 @@ import cloningService from '../services/cloning.js';
 | 
			
		||||
import linkService from '../services/link.js';
 | 
			
		||||
import noteDetailService from '../services/note_detail.js';
 | 
			
		||||
import treeUtils from '../services/tree_utils.js';
 | 
			
		||||
import autocompleteService from '../services/autocomplete.js';
 | 
			
		||||
import server from "../services/server.js";
 | 
			
		||||
import noteDetailText from "../services/note_detail_text.js";
 | 
			
		||||
 | 
			
		||||
const $dialog = $("#add-link-dialog");
 | 
			
		||||
const $form = $("#add-link-form");
 | 
			
		||||
@ -11,6 +12,7 @@ const $linkTitle = $("#link-title");
 | 
			
		||||
const $clonePrefix = $("#clone-prefix");
 | 
			
		||||
const $linkTitleFormGroup = $("#add-link-title-form-group");
 | 
			
		||||
const $prefixFormGroup = $("#add-link-prefix-form-group");
 | 
			
		||||
const $linkTypeDiv = $("#add-link-type-div");
 | 
			
		||||
const $linkTypes = $("input[name='add-link-type']");
 | 
			
		||||
const $linkTypeHtml = $linkTypes.filter('input[value="html"]');
 | 
			
		||||
 | 
			
		||||
@ -52,8 +54,12 @@ async function showDialog() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $autoComplete.autocomplete({
 | 
			
		||||
        source: await autocompleteService.getAutocompleteItems(),
 | 
			
		||||
        minLength: 0,
 | 
			
		||||
        source: async function(request, response) {
 | 
			
		||||
            const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term));
 | 
			
		||||
 | 
			
		||||
            response(result);
 | 
			
		||||
        },
 | 
			
		||||
        minLength: 2,
 | 
			
		||||
        change: async () => {
 | 
			
		||||
            const val = $autoComplete.val();
 | 
			
		||||
            const notePath = linkService.getNodePathFromLabel(val);
 | 
			
		||||
@ -92,7 +98,16 @@ $form.submit(() => {
 | 
			
		||||
 | 
			
		||||
            $dialog.dialog("close");
 | 
			
		||||
 | 
			
		||||
            linkService.addLinkToEditor(linkTitle, '#' + notePath);
 | 
			
		||||
            const linkHref = '#' + notePath;
 | 
			
		||||
 | 
			
		||||
            if (hasSelection()) {
 | 
			
		||||
                const editor = noteDetailText.getEditor();
 | 
			
		||||
 | 
			
		||||
                editor.execute('link', linkHref);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                linkService.addLinkToEditor(linkTitle, linkHref);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (linkType === 'selected-to-current') {
 | 
			
		||||
            const prefix = $clonePrefix.val();
 | 
			
		||||
@ -113,17 +128,21 @@ $form.submit(() => {
 | 
			
		||||
    return false;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// returns true if user selected some text, false if there's no selection
 | 
			
		||||
function hasSelection() {
 | 
			
		||||
    const model = noteDetailText.getEditor().model;
 | 
			
		||||
    const selection = model.document.selection;
 | 
			
		||||
 | 
			
		||||
    return !selection.isCollapsed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function linkTypeChanged() {
 | 
			
		||||
    const value = $linkTypes.filter(":checked").val();
 | 
			
		||||
 | 
			
		||||
    if (value === 'html') {
 | 
			
		||||
        $linkTitleFormGroup.show();
 | 
			
		||||
        $prefixFormGroup.hide();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $linkTitleFormGroup.hide();
 | 
			
		||||
        $prefixFormGroup.show();
 | 
			
		||||
    }
 | 
			
		||||
    $linkTitleFormGroup.toggle(!hasSelection() && value === 'html');
 | 
			
		||||
    $prefixFormGroup.toggle(!hasSelection() && value !== 'html');
 | 
			
		||||
 | 
			
		||||
    $linkTypeDiv.toggle(!hasSelection());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$linkTypes.change(linkTypeChanged);
 | 
			
		||||
 | 
			
		||||
@ -1,104 +0,0 @@
 | 
			
		||||
import treeCache from "./tree_cache.js";
 | 
			
		||||
import treeUtils from "./tree_utils.js";
 | 
			
		||||
import protectedSessionHolder from './protected_session_holder.js';
 | 
			
		||||
 | 
			
		||||
async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
 | 
			
		||||
    if (!parentNoteId) {
 | 
			
		||||
        parentNoteId = 'root';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const parentNote = await treeCache.getNote(parentNoteId);
 | 
			
		||||
    const childNotes = await parentNote.getChildNotes();
 | 
			
		||||
 | 
			
		||||
    if (!childNotes.length) {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!notePath) {
 | 
			
		||||
        notePath = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!titlePath) {
 | 
			
		||||
        titlePath = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const autocompleteItems = [];
 | 
			
		||||
 | 
			
		||||
    for (const childNote of childNotes) {
 | 
			
		||||
        if (childNote.hideInAutocomplete) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId;
 | 
			
		||||
        const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId);
 | 
			
		||||
 | 
			
		||||
        if (!childNote.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) {
 | 
			
		||||
            autocompleteItems.push({
 | 
			
		||||
                value: childTitlePath + ' (' + childNotePath + ')',
 | 
			
		||||
                label: childTitlePath
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath);
 | 
			
		||||
 | 
			
		||||
        for (const childItem of childItems) {
 | 
			
		||||
            autocompleteItems.push(childItem);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (parentNoteId === 'root') {
 | 
			
		||||
        console.log(`Generated ${autocompleteItems.length} autocomplete items`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return autocompleteItems;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Overrides the default autocomplete filter function to search for matched on atleast 1 word in each of the input term's words
 | 
			
		||||
$.ui.autocomplete.filter = (array, terms) => {
 | 
			
		||||
    if (!terms) {
 | 
			
		||||
        return array;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const startDate = new Date();
 | 
			
		||||
 | 
			
		||||
    const results = [];
 | 
			
		||||
    const tokens = terms.toLowerCase().split(" ");
 | 
			
		||||
 | 
			
		||||
    for (const item of array) {
 | 
			
		||||
        const lcLabel = item.label.toLowerCase();
 | 
			
		||||
 | 
			
		||||
        const found = tokens.every(token => lcLabel.indexOf(token) !== -1);
 | 
			
		||||
        if (!found) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // this is not completely correct and might cause minor problems with note with names containing this " / "
 | 
			
		||||
        const lastSegmentIndex = lcLabel.lastIndexOf(" / ");
 | 
			
		||||
 | 
			
		||||
        if (lastSegmentIndex !== -1) {
 | 
			
		||||
            const lastSegment = lcLabel.substr(lastSegmentIndex + 3);
 | 
			
		||||
 | 
			
		||||
            // at least some token needs to be in the last segment (leaf note), otherwise this
 | 
			
		||||
            // particular note is not that interesting (query is satisfied by parent note)
 | 
			
		||||
            const foundInLastSegment = tokens.some(token => lastSegment.indexOf(token) !== -1);
 | 
			
		||||
 | 
			
		||||
            if (!foundInLastSegment) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        results.push(item);
 | 
			
		||||
 | 
			
		||||
        if (results.length > 100) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    console.log("Search took " + (new Date().getTime() - startDate.getTime()) + "ms");
 | 
			
		||||
 | 
			
		||||
    return results;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    getAutocompleteItems
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										1
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							@ -35,6 +35,7 @@ import libraryLoader from "./library_loader.js";
 | 
			
		||||
// required for CKEditor image upload plugin
 | 
			
		||||
window.glob.getCurrentNode = treeService.getCurrentNode;
 | 
			
		||||
window.glob.getHeaders = server.getHeaders;
 | 
			
		||||
window.glob.showAddLinkDialog = addLinkDialog.showDialog;
 | 
			
		||||
 | 
			
		||||
// required for ESLint plugin
 | 
			
		||||
window.glob.getCurrentNote = noteDetailService.getCurrentNote;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								src/public/libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/public/libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -56,7 +56,7 @@ async function checkContentHashes(otherHashes) {
 | 
			
		||||
        if (hashes[key] !== otherHashes[key]) {
 | 
			
		||||
            allChecksPassed = false;
 | 
			
		||||
 | 
			
		||||
            await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${resp.hashes[key]}`);
 | 
			
		||||
            await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${otherHashes[key]}`);
 | 
			
		||||
 | 
			
		||||
            if (key !== 'recent_notes') {
 | 
			
		||||
                // let's not get alarmed about recent notes which get updated often and can cause failures in race conditions
 | 
			
		||||
 | 
			
		||||
@ -220,7 +220,7 @@
 | 
			
		||||
 | 
			
		||||
    <div id="add-link-dialog" title="Add link" style="display: none;">
 | 
			
		||||
      <form id="add-link-form">
 | 
			
		||||
        <div class="radio">
 | 
			
		||||
        <div id="add-link-type-div" class="radio">
 | 
			
		||||
          <label title="Add HTML link to the selected note at cursor in current note">
 | 
			
		||||
            <input type="radio" name="add-link-type" value="html"/>
 | 
			
		||||
            add normal HTML link</label>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user