const toaster = new Toaster(); const searchForm = document.querySelector("#search-form"); const searchInput = document.querySelector("#search-input"); const cardsContainer = document.querySelector("#cards"); const commandElements = { createCardModalButton: document.querySelector("#open-new-card-modal-button"), importPinsModalButton: document.querySelector("#open-import-pins-modal-button"), openOptionsModalButton: document.querySelector("#open-options-modal-button"), }; const cardModalElements = { modal: document.querySelector("#card-modal"), save: document.querySelector("#card-modal #save-card-button"), name: document.querySelector("#card-modal #card-name"), header: document.querySelector("#card-modal .modal-title"), cardLinksContainer: document.querySelector("#card-modal #card-links"), cardLinksUrlInputs: document.querySelectorAll( "#card-modal #card-links .link input[name='url']" ), }; const optionsModalElements = { modal: document.querySelector("#options-modal"), save: document.querySelector("#options-modal #save-options-button"), }; const fileInput = document.querySelector("#file"); const cardModalBootstrap = new bootstrap.Modal(cardModalElements.modal); const showCardModal = () => cardModalBootstrap.show(); const hideCardModal = () => cardModalBootstrap.hide(); const optionsModalBootstrap = new bootstrap.Modal(optionsModalElements.modal); const showOptionsModal = () => optionsModalBootstrap.show(); const hideOptionsModal = () => optionsModalBootstrap.hide(); const constants = { messages: {}, database: { name: "startpage", keys: { cards: "cards", pins: "pins", settings: "settings", }, }, flags: { debug: true, }, toast_types: { success: "success", error: "error", info: "info", }, }; let database; function initialize() { database = new PouchDB(constants.database.name); addEventListeners(); renderCards(); } function addEventListeners() { cardModalElements.save.addEventListener("click", submitCardForm); cardModalElements.cardLinksUrlInputs.forEach((element) => { element.addEventListener("keypress", keypressOnLinkUrlInput); }); commandElements.createCardModalButton.addEventListener("click", openCreateCardModal); commandElements.openOptionsModalButton.addEventListener("click", openSettingsModal); commandElements.importPinsModalButton.addEventListener("click", () => fileInput.click()); fileInput.addEventListener("change", function () { const file = fileInput.files[0]; console.log(file); const reader = new FileReader(); reader.onload = (content) => importPins(content.target.result); reader.readAsText(file); }); cardModalElements.modal.addEventListener("hidden.bs.modal", function (event) { cardModalElements.name.value = ""; cardModalElements.cardLinksContainer.empty(); }); document.addEventListener("hidden.bs.modal", function (event) { searchInput.focus(); }); } function openSettingsModal() { showOptionsModal(); } let pin = { description: "", extended: "", hash: "", href: "", meta: "", tags: "", time: "", toread: "", }; function importPins(json) { JSON.parse(json).forEach((pin) => { if (!addPin(pin)) throw new Error("WHOOPS"); }); } function logPins() { database.get(constants.database.keys.pins).then((pins) => { pins.data.forEach((element) => { console.log(element); }); }); } function addPin(pin) { database .get(constants.database.keys.pins) .then((pins) => { pins._rev = pins._rev; pins._id = constants.database.keys.pins; if (!pin._id) pin._id = guid(); let current = pins.data.find((item) => item._id == pin._id); if (current) return true; pins.data.push(pin); return database.put(pins); }) .then((updated) => { return true; }) .catch((err) => { if (err.status === 404) { let newPinsDb = { _id: constants.database.keys.pins, data: [], }; pin._id = guid(); newPinsDb.data.push(pin); database .put(newPinsDb) .then((added) => { console.log("created pins"); }) .catch((err) => { logError("failed to create pins", err); return false; }); } else { logError("failed to update pins", err); return false; } }); } function openEditCardModal(id) { if (!id) return; database .get(constants.database.keys.cards) .then((cardDoc) => { console.log(cardDoc); let card = cardDoc.data.find((card) => card._id == id); if (!card) { renderCards(); console.log("did not find card"); } cardModalElements.name.value = card.name; cardModalElements.header.innerText = "Rediger " + card.name; cardModalElements.modal.dataset.id = card._id; card.links.forEach((link) => addNewLinkInputs(false, link.name, link.url)); }) .catch((err) => logError(err)); showCardModal(); cardModalElements.name.focus(); cardModalElements.modal.scrollTop = cardModalElements.modal.scrollHeight; } function openCreateCardModal() { cardModalElements.header.innerText = "Nytt kort"; cardModalElements.name.value = ""; showCardModal(); cardModalElements.name.focus(); addNewLinkInputs(); } function submitCardForm(event) { event.preventDefault(); if (!cardModalElements.name.value) return; let card = { name: cardModalElements.name.value, links: [], }; if (cardModalElements.modal.dataset.id) card._id = cardModalElements.modal.dataset.id; document.querySelectorAll("#card-modal #card-links .link").forEach((linkElement) => { let name = linkElement.firstElementChild.value; let url = linkElement.lastElementChild.value; if (!name || !url || !url.startsWith("http")) return; name = name.trim(); url = url.trim(); card.links.push({ name, url, }); }); if (card.links.length == 0) return; addOrUpdateCard(card); hideCardModal(); } function addNewLinkInputs(changeFocus, name, url) { let inputGroup = document.createElement("div"); inputGroup.className = "link mt-1 p-2 d-flex align-items-center"; let nameInput = document.createElement("input"); nameInput.name = "name"; nameInput.className = "form-control"; nameInput.placeholder = "Navn"; if (name) nameInput.value = name; inputGroup.appendChild(nameInput); let arrow = document.createElement("img"); arrow.src = "assets/icons/arrow-right.svg"; arrow.className = "mx-3"; arrow.alt = ""; arrow.width = "32"; arrow.height = "32"; inputGroup.appendChild(arrow); let urlInput = document.createElement("input"); urlInput.name = "url"; urlInput.className = "form-control"; urlInput.placeholder = "Url"; urlInput.value = url ? url : "http://"; urlInput.addEventListener("keypress", keypressOnLinkUrlInput); inputGroup.appendChild(urlInput); cardModalElements.cardLinksContainer.appendChild(inputGroup); if (changeFocus) nameInput.focus(); cardModalElements.modal.scrollTop = cardModalElements.modal.scrollHeight; } function keypressOnLinkUrlInput(event) { if (event.keyCode == 13) { let shouldCreateNewInputs = event.target.parentElement.nextElementSibling == null; if (shouldCreateNewInputs) { addNewLinkInputs(true); } } } function addOrUpdateCard(card) { if (!card.name) return; database .get(constants.database.keys.cards) .then((cards) => { cards._rev = cards._rev; cards._id = constants.database.keys.cards; if (!card._id) card._id = guid(); let current = cards.data.find((item) => item._id == card._id); if (current) { current.name = card.name; current.links = card.links; } else cards.data.push(card); return database.put(cards); }) .then((updated) => { console.log("updated carddb"); renderCards(); }) .catch((err) => { if (err.status === 404) { let newCardDocument = { _id: constants.database.keys.cards, data: [], }; card._id = guid(); newCardDocument.data.push(card); database .put(newCardDocument) .then((added) => { console.log("created carddb"); renderCards(); }) .catch((err) => logError("failed to create carddb", err)); } else { logError("failed to update carddb", err); } }); } function logError(msg, error) { console.error("!!!"); console.error(msg); toaster.error("Whoops!", msg); console.error(error); console.error("!!!"); } function wipeData() { new PouchDB(constants.database.names.cards) .destroy() .then(function () { console.log("wiped data"); }) .catch(function (err) { console.error(err); }); } function renderCards() { cardsContainer.empty(); database .get(constants.database.keys.cards) .then((cardDoc) => { cardDoc.data .sort((a, b) => a.order - b.order) .forEach((card) => { let columElement = document.createElement("div"); columElement.className = "col-sm-6 col-lg-4 mb-4"; let cardElement = document.createElement("div"); cardElement.className = "card"; columElement.appendChild(cardElement); let cardHeader = document.createElement("div"); cardHeader.className = "card-header d-flex text-truncate justify-content-between align-items-center"; let cardHeaderText = document.createElement("span"); cardHeaderText.innerText = card.name; cardHeader.appendChild(cardHeaderText); let cardHeaderEditButton = document.createElement("img"); cardHeaderEditButton.src = "assets/icons/pencil-square.svg"; cardHeaderEditButton.title = "Rediger " + card.name; cardHeaderEditButton.width = "18"; cardHeaderEditButton.type = "button"; cardHeaderEditButton.height = "18"; cardHeaderEditButton.onclick = () => openEditCardModal(card._id); cardHeader.appendChild(cardHeaderEditButton); cardElement.appendChild(cardHeader); let linkList = document.createElement("ul"); linkList.className = "list-group list-group-flush"; card.links.forEach((link) => { let listItem = document.createElement("a"); listItem.innerText = link.name; listItem.className = "list-group-item"; listItem.href = link.url; linkList.appendChild(listItem); }); cardElement.appendChild(linkList); cardsContainer.append(columElement); }); new Masonry(cardsContainer); }) .catch((error) => { if (error.status !== 404) logError(error); }); } function guid() { let index, tea, result = ""; for (index = 0; index < 32; index++) (tea = (16 * Math.random()) | 0), (8 != index && 12 != index && 16 != index && 20 != index) || (result += "-"), (result += (12 == index ? 4 : 16 == index ? (3 & tea) | 8 : tea).toString(16)); return result; } Element.prototype.empty = function () { while (this.firstChild) { this.removeChild(this.firstChild); } }; initialize();