Skip to content

Instantly share code, notes, and snippets.

@Heziode
Created April 8, 2023 14:47
Show Gist options
  • Select an option

  • Save Heziode/f37d9ddbef0c29a40bf138fc0fc4b058 to your computer and use it in GitHub Desktop.

Select an option

Save Heziode/f37d9ddbef0c29a40bf138fc0fc4b058 to your computer and use it in GitHub Desktop.

Revisions

  1. @chrisgrieser chrisgrieser revised this gist Jan 30, 2022. 2 changed files with 11 additions and 7 deletions.
    14 changes: 9 additions & 5 deletions word-count-dashboard.md
    Original file line number Diff line number Diff line change
    @@ -20,10 +20,14 @@ status: ✅
    ---
    ```

    ### Known Issues
    It seems that this dahsboard fails when amount of words / notes becomes too high. As far as I can tell, this is due to limitations by dataview/Obsidian, and one could only be tackled by a dedicated plugin for Word Counts. If you really want multi-note word counts, please request a plugin like that in the forum!
    ### Known Issues & Troubleshooting
    - It seems that this dahsboard fails when amount of words / notes becomes too high. As far as I can tell, this is due to limitations by dataview/Obsidian, and one could only be tackled by a dedicated plugin for Word Counts. If you really want multi-note word counts, please [request a plugin like that in the forum](https://forum.obsidian.md/c/plugins/10)!
    - When dataview reports some error, try to use the default configuration with just the `sourceFolder` changed – that should work, and then try step-by-step to find out whether a certain configuration causes the dashboard to break.

    ## License
    MIT License 2022.
    ### Troubleshooting & Feature Requests
    I am very sorry, but I unfortunately cannot really provide much support or implement feature requests. As much as I'd love to help out, the limitations of being an overly complex script running on top of dataview makes this dashboard quite brittle, and the fact that this isn't really a proper plugin make any sort of debugging extremely time-intensive (and writing a PhD and maintaining multiple plugins/themes already, my time is limited). If I do have the time, I'll maybe turn this into a proper plugin, but that would require quite some time, since it would require a lot of coding skills I do not have yet (being only a hobby coder.)

    Please feel free to take this code, customize it, and publish it! Simply credit me briefly. Regrettably, I do not have the time to properly maintain this or make even implement feature requests.
    If you do want to have a nice word count dashboard in Obsidian, please make a [request in the forum](https://forum.obsidian.md/c/plugins/10) and/or make a feature request at one of the already existing plugins to integrate this dashboard (e.g. [the longform plugin](https://github.com/kevboh/longform/issues/47)). Or, if you have coding experience yourself, feel free to take this code, customize it, and make a plugin of your own!

    ### License
    MIT License 2022
    4 changes: 2 additions & 2 deletions wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -35,7 +35,7 @@ charsPerCitation:: 155

    __Longform Plugin__
    *Leave empty to sort alphabetically. Enter the path to the index file of a longform project to order sections by their order in the longform plugin. (The `sourceFolder` setting further above has to be a Longform Drafts folder. )*
    pathToIndexFile:: Writing/Interdependence & Innovation/Index
    pathToIndexFile::

    *Begin a filename with this character and it will be treated as subsection*
    subsectionStartChar:: _
    @@ -50,5 +50,5 @@ mostRecentIcon:: 🕙


    ```dataviewjs
    <!-- put the above code in a codeblock like this-->
    <!-- put the code from above in a codeblock like this-->
    ```
  2. @chrisgrieser chrisgrieser revised this gist Jan 29, 2022. 3 changed files with 12 additions and 10 deletions.
    16 changes: 8 additions & 8 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    // Word Count Dashboard
    // a dataviewjs snippet by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.10.1
    // last update: 2022-01-02
    // version 1.10.2
    // last update: 2022-01-25

    //----------------------------------------------------
    // Import configuration
    @@ -14,8 +14,8 @@ const includeFootnotes = source.includeFootnotes;
    const charactersIncludeSpaces = source.charactersIncludeSpaces;
    const excludeComments = source.excludeComments;
    const includeBibliographyEstimate = source.includeBibliographyEstimate;
    const wordsPerCitation = source.wordsPerCitation;
    const charsPerCitation = source.charsPerCitation;
    const wordsPerCitation = source.wordsPerCitation;
    const charsPerCitation = source.charsPerCitation;
    const thousandSeperator = source.thousandSeperator;
    const useThousandSeperator = source.useThousandSeperator;
    const naChar = source.naChar;
    @@ -89,12 +89,12 @@ function removeMarkdown (text) {

    function removeFootnotes (text) {
    return text
    .replace(/^\[\^\w+\]:.*$/gm, "") // footnote at the end
    .replace(/\[\^\w+\]/g, ""); // footnote reference inline
    .replace(/^\[\^[A-Za-z0-9-]+\]:.*$/gm, "") // footnote at the end
    .replace(/\[\^[A-Za-z0-9-]+\]/g, ""); // footnote reference inline
    }

    function countPandocCitations (text) {
    const citations = text.match(/@\w+(?=[,;\] ])/gi);
    const citations = text.match(/@[A-Za-z0-9-]+(?=[,;\] ])/gi);
    if (!citations) return 0;
    const uniqCitations = [...new Set(citations)]; // only unique citations
    return uniqCitations.length;
    @@ -137,7 +137,7 @@ async function getTableContents () {
    if (pathToIndexFile) {
    const draftName = sourceFolder.split("/").pop();
    const longformOrder =
    dv.page(pathToIndexFile)
    dv.page(pathToIndexFile) // do not wrap in ", as with db.pages
    .drafts
    .filter(d => d.name === draftName)
    .scenes;
    5 changes: 4 additions & 1 deletion wordcount_dashboard.css
    Original file line number Diff line number Diff line change
    @@ -3,9 +3,9 @@ https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99 */

    .wordcountTable table.dataview.table-view-table td:first-child,
    .wordcountTable table.dataview.table-view-table th:first-child {
    text-align: center;
    padding: 4px 7px;
    border-left: none;
    text-align: center;
    }

    .wordcountTable table.dataview.table-view-table td:first-child {
    @@ -61,3 +61,6 @@ https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99 */
    .wordcountTable table.dataview.table-view-table tr:last-child td:nth-child(4) {
    text-align: center;
    }

    /* Supercharged Links */
    .data-link-icon[data-link-cssclass*="wordcount" i]::BEFORE { content: "🔢 " }
    1 change: 0 additions & 1 deletion wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,6 @@ cssclass: wordcountTable

    %%

    ### Configuration
    __Notes to display__
    *Gets either notes in a folder or notes with a certain tag. Leave one of them empty.*
    sourceFolder:: Writing/Interdependence & Innovation/Drafts/Submission
  3. @chrisgrieser chrisgrieser revised this gist Jan 11, 2022. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion word-count-dashboard.md
    Original file line number Diff line number Diff line change
    @@ -21,4 +21,9 @@ status: ✅
    ```

    ### Known Issues
    It seems that this dahsboard fails when amount of words / notes becomes too high. As far as I can tell, this is due to limitations by dataview/Obsidian, and one could only be tackled by a dedicated plugin for Word Counts. If you really want multi-note word counts, please request a plugin like that in the forum!
    It seems that this dahsboard fails when amount of words / notes becomes too high. As far as I can tell, this is due to limitations by dataview/Obsidian, and one could only be tackled by a dedicated plugin for Word Counts. If you really want multi-note word counts, please request a plugin like that in the forum!

    ## License
    MIT License 2022.

    Please feel free to take this code, customize it, and publish it! Simply credit me briefly. Regrettably, I do not have the time to properly maintain this or make even implement feature requests.
  4. @chrisgrieser chrisgrieser revised this gist Jan 6, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Word Count Dashboard
    // a dataviewjs snippet by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.10
    // version 1.10.1
    // last update: 2022-01-02

    //----------------------------------------------------
    @@ -137,7 +137,7 @@ async function getTableContents () {
    if (pathToIndexFile) {
    const draftName = sourceFolder.split("/").pop();
    const longformOrder =
    dv.page("\"" + pathToIndexFile + "\"")
    dv.page(pathToIndexFile)
    .drafts
    .filter(d => d.name === draftName)
    .scenes;
  5. @chrisgrieser chrisgrieser revised this gist Jan 6, 2022. 3 changed files with 181 additions and 72 deletions.
    184 changes: 126 additions & 58 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,14 +1,15 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.5.2
    // Word Count Dashboard
    // a dataviewjs snippet by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.10
    // last update: 2022-01-02

    //----------------------------------------------------
    // Import configuration
    //----------------------------------------------------
    const source = dv.current();
    const sourceFolder = source.sourceFolder;
    const charTarget = source.charTarget;
    const wordTarget = source.wordTarget;
    const target = source.target;
    const toCount = source.toCount;
    const includeFootnotes = source.includeFootnotes;
    const charactersIncludeSpaces = source.charactersIncludeSpaces;
    const excludeComments = source.excludeComments;
    @@ -20,7 +21,11 @@ const useThousandSeperator = source.useThousandSeperator;
    const naChar = source.naChar;
    const subsectionStartChar = source.subsectionStartChar;
    const wordsPerPage = source.wordsPerPage;
    const charsPerPage = source.charsPerPage;
    const pathToIndexFile = source.pathToIndexFile;
    const cumulativeShare = source.cumulativeShare;
    const groupedCount = source.groupedCount;
    const mostRecentIcon = source.mostRecentIcon;

    let sourceTag = source.sourceTag;
    let excludeTag = source.excludeTag;
    @@ -95,14 +100,8 @@ function countPandocCitations (text) {
    return uniqCitations.length;
    }

    function calculateShare (charCount, wordCount) {
    if (!wordTarget && !charTarget) return naChar;
    if (charTarget !== 0) return charCount / charTarget;
    if (wordTarget !== 0) return wordCount / wordTarget;
    }

    function toPercentStr (share) {
    return (share * 100).toFixed(1).toString() + "%";
    return (share * 100).toFixed(0).toString() + " %";
    }

    //----------------------------------------------------
    @@ -111,11 +110,11 @@ function toPercentStr (share) {
    async function getTableContents () {
    const output = [];
    let completeText = "";
    let totalWords = 0;
    let totalChars = 0;
    let cumulativeShare = 0;
    let total = 0;
    let share = 0;
    let sectionCounter = 0;
    let subsectionCounter = 0;
    let totalTasks = 0;

    // get sections via folder or via tag
    let sections;
    @@ -130,11 +129,15 @@ async function getTableContents () {
    sections = sections.filter(t => !t.file.tags.includes(excludeTag));
    }

    // most recent note
    sections = sections.sort (s => s.file.mtime, "desc");
    const mostRecentNote = sections[0].file.name;

    // SORT sections
    if (pathToIndexFile) {
    const draftName = sourceFolder.split("/").pop();
    const longformOrder =
    dv.page(pathToIndexFile)
    dv.page("\"" + pathToIndexFile + "\"")
    .drafts
    .filter(d => d.name === draftName)
    .scenes;
    @@ -157,6 +160,16 @@ async function getTableContents () {
    // read page content
    let content = await dv.io.load(section.file.path); // eslint-disable-line no-await-in-loop

    // count markdown tasks
    // need to be counted before cleanup
    let tasks = content.match(/- \[ ] /g);
    let taskNum = 0;
    let taskStr = "";
    if (tasks) {
    taskNum = tasks.length;
    taskStr = taskNum.toString();
    }

    // clean up
    content = removeMarkdown (content);
    if (!includeFootnotes) content = removeFootnotes (content);
    @@ -165,9 +178,12 @@ async function getTableContents () {
    .replace(/ {2,}/g, " "); // reduce multiple spaces to a single space

    // Table Values: Count & Share
    const characterCount = getCharacterCount(content);
    const wordCount = getWordCount(content);
    cumulativeShare += calculateShare(characterCount, wordCount);
    let wcCount = 0;
    if (toCount === "words") wcCount = getWordCount(content);
    if (toCount === "chars") wcCount = getCharacterCount(content);

    if (cumulativeShare) share += (wcCount / target);
    else share = (wcCount / target);

    // Status
    let status = section.status;
    @@ -176,91 +192,136 @@ async function getTableContents () {
    // Section numbering
    const isSubsection = section.file.name.startsWith(subsectionStartChar);
    let sectionNumbering;
    let sectionLink;
    if (isSubsection) {
    subsectionCounter++;
    sectionNumbering = sectionCounter.toString() + "." + subsectionCounter.toString();
    }
    else {
    sectionNumbering = "<small>" + sectionCounter.toString() + "." + subsectionCounter.toString() + "</small>";
    sectionLink =
    "<small>[["
    + section.file.path
    + "|"
    + section.file.name.slice(1)
    + "]]</small>";
    } else {
    subsectionCounter = 0;
    sectionCounter++;
    sectionNumbering = sectionCounter.toString().strong();
    sectionLink = "__" + section.file.link + "__";
    }

    // Most Recent Note
    if (section.file.name === mostRecentNote) sectionLink += "&nbsp;&nbsp;&nbsp;" + mostRecentIcon;

    // push table values
    output.push([
    sectionNumbering,
    section.file.link,
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    toPercentStr(cumulativeShare),
    sectionLink,
    wcCount,
    share,
    taskStr,
    status
    ]);

    // add to totals & bibliography calculation
    totalChars += characterCount;
    totalWords += wordCount;
    totalTasks += taskNum;
    total += wcCount;
    if (includeBibliographyEstimate) completeText += content;
    }

    // Add Subsections counts to the sections
    //-------------------------------------------------

    if (groupedCount) {
    let upperSectionID = -1;
    for (var i = 0; i < output.length; i++) {
    let isSubsection = output[i][0].includes(".");
    let firstSectionFound = (upperSectionID !== -1);

    if (!firstSectionFound && isSubsection) continue;

    if (isSubsection) {
    output[upperSectionID][2] += output[i][2]; // add count
    if (!cumulativeShare) output[upperSectionID][3] += output[i][3]; // add share
    }

    if (!isSubsection) upperSectionID = i;
    }

    output.map(row => {
    row[2] = insert1000sep(row[2]);
    row[3] = toPercentStr(row[3]);

    let isSubsection = row[0].includes(".");
    if (isSubsection) {
    row[2] = "<small>" + row[2] + "</small>";
    if (!cumulativeShare) row[3] = "<small>" + row[3] + "</small>";
    } else {
    row[2] = "<u>" + row[2] + "</u>" ;
    if (!cumulativeShare) row[3] = "<u>" + row[3] + "</u>";
    }
    return row;
    });
    }

    //-------------------------------------------------
    // OVERALL
    //-------------------------------------------------

    // Bibliography Estimate
    if (includeBibliographyEstimate) {
    const citationCount = countPandocCitations(completeText);
    const wordCount = citationCount * wordsPerCitation;
    let characterCount = citationCount * charsPerCitation;
    if (!charactersIncludeSpaces) characterCount = citationCount * (charsPerCitation - 20); // eslint-disable-line no-magic-numbers
    cumulativeShare += calculateShare(characterCount, wordCount);
    let wcCount = 0;
    if (toCount === "words") wcCount = citationCount * wordsPerCitation;
    if (toCount === "chars") wcCount = citationCount * charsPerCitation;
    if (!charactersIncludeSpaces && toCount === "chars") wcCount = citationCount * (charsPerCitation - 20); // eslint-disable-line no-magic-numbers

    if (cumulativeShare === "true") share += (wcCount / target);
    else share = (wcCount / target);

    output.push([
    "",
    "Bibliography (" + citationCount + " citations)",
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    toPercentStr(cumulativeShare),
    "~" + insert1000sep(wcCount),
    toPercentStr(share),
    naChar,
    naChar
    ]);

    // add for Totals calculation
    totalChars += characterCount;
    totalWords += wordCount;
    total += wcCount;
    }

    // Totals calculation
    const totalProgress = calculateShare(totalChars, totalWords);
    let total = "Total";
    if (wordsPerPage) {
    const totalPages = (totalWords / wordsPerPage).toFixed(1);
    total += "&nbsp;&nbsp;&nbsp;&nbsp;(" + totalPages + " Pages)";
    const totalShare = total / target;
    let totalTitle = "Total";
    if (wordsPerPage && toCount === "words") {
    const totalPages = (total / wordsPerPage).toFixed(1);
    totalTitle += "&nbsp;&nbsp;&nbsp;(~" + totalPages + " Pages)";
    }
    if (charsPerPage && toCount === "chars") {
    const totalPages = (total / charsPerPage).toFixed(1);
    totalTitle += "&nbsp;&nbsp;&nbsp;(~" + totalPages + " Pages)";
    }
    output.push([
    "",
    total.strong(),
    insert1000sep(totalChars).strong(),
    insert1000sep(totalWords).strong(),
    toPercentStr(totalProgress).strong(),
    totalTitle.strong(),
    insert1000sep(total).strong(),
    toPercentStr(totalShare).strong(),
    totalTasks.toString().strong(),
    naChar.strong()
    ]);

    // Target & Progress Bar
    const progressBar =
    "&nbsp;&nbsp;&nbsp;&nbsp;"
    + " <progress max=\"100\" value=\""
    + (totalProgress * 100).toFixed().toString()
    + (totalShare * 100).toFixed().toString()
    + "\"> </progress>";

    let charTargetText = naChar;
    let wordTargetText = naChar;
    if (charTarget) charTargetText = insert1000sep(charTarget);
    if (wordTarget) wordTargetText = insert1000sep(wordTarget);

    output.push([
    "",
    "Target".strong() + progressBar,
    charTargetText.strong(),
    wordTargetText.strong(),
    insert1000sep(target).strong(),
    naChar.strong(),
    naChar.strong(),
    naChar.strong()
    ]);
    @@ -275,8 +336,13 @@ async function getTableContents () {
    // Print Table
    let numExcludedNotes = 0;
    let numExcludeStatus = 0;
    let countedEntity = "Words";
    if (toCount === "chars") countedEntity = "Chars";
    let typeOfShare = "Share";
    if (cumulativeShare === "true") countedEntity = "Target";

    const tcontent = await getTableContents();
    dv.table(["⟡", "Section", "Chars", "Words", "Target", "Status"], tcontent);
    dv.table(["⟡", "Section", countedEntity, typeOfShare, "Tasks" ,"Status"], tcontent);

    // Append Settings Info
    let settingFt = "Footnotes excluded. ";
    @@ -290,8 +356,10 @@ let settingPages = "";
    if (includeFootnotes) settingFt = "Footnotes included. ";
    if (!includeBibliographyEstimate) settingBibliography = "Bibliography excluded. ";
    if (!charactersIncludeSpaces) settingCharSpaces = "Character Count without Spaces. ";
    if (toCount === "words") settingCharSpaces = "";
    if (excludeComments) settingComments = "Comments excluded. ";
    if (wordsPerPage) settingPages = "Assuming " + wordsPerPage.toString() + " words per page. ";
    if (wordsPerPage && toCount === "words") settingPages = "Assuming " + wordsPerPage.toString() + " words per page. ";
    if (wordsPerPage && toCount === "chars") settingPages = "Assuming " + charsPerPage.toString() + " characters per page. ";

    if (numExcludeStatus) {
    let plural = "s";
    @@ -313,7 +381,6 @@ if (numExcludedNotes) {
    + " tagged with " + excludeTag
    + " omitted. ";
    }
    dv.span("---");

    dv.span(
    "<small>"
    @@ -327,3 +394,4 @@ dv.span(
    + settingPages
    + "</small>"
    );

    41 changes: 38 additions & 3 deletions wordcount_dashboard.css
    Original file line number Diff line number Diff line change
    @@ -8,13 +8,30 @@ https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99 */
    text-align: center;
    }

    .wordcountTable table.dataview.table-view-table td:first-child {
    color: var(--text-muted) !important;
    }

    .wordcountTable table.dataview.table-view-table td:nth-child(3),
    .wordcountTable table.dataview.table-view-table td:nth-child(4),
    .wordcountTable table.dataview.table-view-table td:nth-child(5),
    .wordcountTable table.dataview.table-view-table td:nth-child(6) {
    .wordcountTable table.dataview.table-view-table td:nth-child(4) {
    text-align: end;
    }

    .wordcountTable table.dataview.table-view-table th:nth-child(3),
    .wordcountTable table.dataview.table-view-table td:nth-child(3),
    .wordcountTable table.dataview.table-view-table th:nth-child(4),
    .wordcountTable table.dataview.table-view-table td:nth-child(4),
    .wordcountTable table.dataview.table-view-table th:nth-child(5),
    .wordcountTable table.dataview.table-view-table td:nth-child(5) {
    padding-right: 7px;
    padding-left: 7px;
    }

    .wordcountTable table.dataview.table-view-table td:nth-child(2) {
    text-align: start;
    }

    .wordcountTable table.dataview.table-view-table td:nth-child(5),
    .wordcountTable table.dataview.table-view-table td:last-child {
    text-align: center;
    }
    @@ -24,5 +41,23 @@ https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99 */
    }

    .wordcountTable table.dataview.table-view-table th {
    font-size: 1.1em;
    text-align: center;
    }

    .wordcountTable table.dataview.table-view-table {
    font-size: 0.9em;
    }

    .wordcountTable table.dataview.table-view-table a.internal-link {
    text-decoration: none;
    }

    .wordcountTable progress {
    margin-bottom: 3px;
    }

    /* target NA field */
    .wordcountTable table.dataview.table-view-table tr:last-child td:nth-child(4) {
    text-align: center;
    }
    28 changes: 17 additions & 11 deletions wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -3,43 +3,49 @@ cssclass: wordcountTable
    ---

    %%

    ### Configuration
    __Notes to display__
    *Gets either notes in a folder or notes with a certain tag. Leave one of them empty.*
    sourceFolder:: Writing/Interdependence & Innovation/Drafts/Showcase
    sourceFolder:: Writing/Interdependence & Innovation/Drafts/Submission
    sourceTag::

    __Notes to exclude__
    *Leave empty to disable.*
    *Leave empty to disable. Notes with the yaml-key `status` and value `exclude` for that key are also excluded.)*
    excludeTag:: #exclude
    *(Notes with the yaml-key "status" and value "exclude" for that key are also excluded.)*

    __Counting Settings__
    *set to zero to ignore*
    charTarget:: 60000
    wordTarget:: 0
    wordsPerPage:: 0
    *"chars" or "words"*
    toCount:: chars
    target:: 70000

    *words or characters per page, depending on setting above. Set to zero to ignore.*
    wordsPerPage:: 350
    charsPerPage:: 2000

    includeFootnotes:: false
    includeFootnotes:: true
    charactersIncludeSpaces:: true
    excludeComments:: true
    cumulativeShare:: false
    groupedCount:: true

    __Bibliography Estimate for Pandoc Citations__
    includeBibliographyEstimate:: true
    wordsPerCitation:: 22
    charsPerCitation:: 155

    __Longform Plugin__
    *Leave empty to sort alphabethically. Enter the path to the index file (not the parent folder) of a longform project to order sections by their order in the longform side bar. (The `sourceFolder::` further above has to be a Longform Drafts folder.)*
    pathToIndexFile::
    *Leave empty to sort alphabetically. Enter the path to the index file of a longform project to order sections by their order in the longform plugin. (The `sourceFolder` setting further above has to be a Longform Drafts folder. )*
    pathToIndexFile:: Writing/Interdependence & Innovation/Index

    *begin a filename with this character and it will be treated as subsection*
    *Begin a filename with this character and it will be treated as subsection*
    subsectionStartChar:: _

    __Purely visual__
    useThousandSeperator:: true
    thousandSeperator:: .
    naChar:: —
    mostRecentIcon:: 🕙

    %%

  6. @chrisgrieser chrisgrieser revised this gist Dec 17, 2021. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,6 @@ cssclass: wordcountTable
    ---

    %%

    ### Configuration
    __Notes to display__
    *Gets either notes in a folder or notes with a certain tag. Leave one of them empty.*
    @@ -31,7 +30,7 @@ wordsPerCitation:: 22
    charsPerCitation:: 155

    __Longform Plugin__
    *Leave empty to sort alphabethically. Enter the path to the index file (not the parent folder) of a longform project to sections by their order in the longform plugin. (The sourceFolder further above has to be a Longform Drafts folder.)*
    *Leave empty to sort alphabethically. Enter the path to the index file (not the parent folder) of a longform project to order sections by their order in the longform side bar. (The `sourceFolder::` further above has to be a Longform Drafts folder.)*
    pathToIndexFile::

    *begin a filename with this character and it will be treated as subsection*
  7. @chrisgrieser chrisgrieser revised this gist Dec 17, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -31,7 +31,7 @@ wordsPerCitation:: 22
    charsPerCitation:: 155

    __Longform Plugin__
    *Leave empty to sort alphabethically. Enter the path to the index file of a longform project to sections by their order in the longform plugin. (The sourceFolder further above has to be a Longform Drafts folder.)*
    *Leave empty to sort alphabethically. Enter the path to the index file (not the parent folder) of a longform project to sections by their order in the longform plugin. (The sourceFolder further above has to be a Longform Drafts folder.)*
    pathToIndexFile::

    *begin a filename with this character and it will be treated as subsection*
  8. @chrisgrieser chrisgrieser revised this gist Dec 16, 2021. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.5.1
    // version 1.5.2

    //----------------------------------------------------
    // Import configuration
    @@ -232,7 +232,7 @@ async function getTableContents () {
    const totalProgress = calculateShare(totalChars, totalWords);
    let total = "Total";
    if (wordsPerPage) {
    totalPages = (totalWords / wordsPerPage).toFixed(1);
    const totalPages = (totalWords / wordsPerPage).toFixed(1);
    total += "&nbsp;&nbsp;&nbsp;&nbsp;(" + totalPages + " Pages)";
    }
    output.push([
  9. @chrisgrieser chrisgrieser revised this gist Dec 16, 2021. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions word-count-dashboard.md
    Original file line number Diff line number Diff line change
    @@ -11,6 +11,9 @@
    6. The status column will be populated with the value of the YAML-key `status` of every document, i.e., you have to use the add the following YAML-Header to every note.
    7. When also using the [Longform Plugin](https://github.com/kevboh/longform), you can name the index file in your settings and the Dashboard will automatically change order based on the order of scenes in your Draft.

    <img src="https://user-images.githubusercontent.com/73286100/146460357-b5b4f61e-fbdd-48fb-9c5b-864cc39a63f1.gif" alt="Wordcount_Dashboard" width=50%>


    ```yaml
    ---
    status:
  10. @chrisgrieser chrisgrieser revised this gist Dec 16, 2021. 1 changed file with 12 additions and 17 deletions.
    29 changes: 12 additions & 17 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.5
    // version 1.5.1

    //----------------------------------------------------
    // Import configuration
    @@ -90,24 +90,15 @@ function removeFootnotes (text) {

    function countPandocCitations (text) {
    const citations = text.match(/@\w+(?=[,;\] ])/gi);
    if (citations) return 0;
    if (!citations) return 0;
    const uniqCitations = [...new Set(citations)]; // only unique citations
    return uniqCitations.length;
    }

    function calculateShare (charCount, wordCount) {
    if (!wordTarget && !charTarget) return naChar;
    let countToUse = 0;
    let targetToUse = 0;
    if (charTarget) {
    countToUse = charCount;
    targetToUse = charTarget;
    }
    if (wordTarget) {
    countToUse = wordCount;
    targetToUse = wordTarget;
    }
    return (countToUse / targetToUse);
    if (charTarget !== 0) return charCount / charTarget;
    if (wordTarget !== 0) return wordCount / wordTarget;
    }

    function toPercentStr (share) {
    @@ -120,7 +111,8 @@ function toPercentStr (share) {
    async function getTableContents () {
    const output = [];
    let completeText = "";
    let totalWords, totalChars = 0;
    let totalWords = 0;
    let totalChars = 0;
    let cumulativeShare = 0;
    let sectionCounter = 0;
    let subsectionCounter = 0;
    @@ -130,6 +122,7 @@ async function getTableContents () {
    if (sourceFolder) sections = dv.pages("\"" + sourceFolder + "\"");
    else sections = dv.pages(sourceTag);

    // exclude certain notes
    numExcludeStatus = sections.filter(t => t.status === "exclude").length;
    sections = sections.filter(t => t.status !== "exclude");
    if (excludeTag !== "") {
    @@ -223,7 +216,7 @@ async function getTableContents () {

    output.push([
    "",
    "Bibliography (Estimate)",
    "Bibliography (" + citationCount + " citations)",
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    toPercentStr(cumulativeShare),
    @@ -236,10 +229,12 @@ async function getTableContents () {
    }

    // Totals calculation
    const totalPages = (totalWords / wordsPerPage).toFixed(1);
    const totalProgress = calculateShare(totalChars, totalWords);
    let total = "Total";
    if (wordsPerPage) total += "&nbsp;&nbsp;&nbsp;&nbsp;(" + totalPages + " Pages)";
    if (wordsPerPage) {
    totalPages = (totalWords / wordsPerPage).toFixed(1);
    total += "&nbsp;&nbsp;&nbsp;&nbsp;(" + totalPages + " Pages)";
    }
    output.push([
    "",
    total.strong(),
  11. @chrisgrieser chrisgrieser revised this gist Dec 16, 2021. 4 changed files with 132 additions and 84 deletions.
    149 changes: 93 additions & 56 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,32 +1,37 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.4.1
    // version 1.5

    //----------------------------------------------------
    // Import configuration
    //----------------------------------------------------
    const source = dv.current();
    const sourceFolder = source.sourceFolder;
    let sourceTag = source.sourceTag;
    const charTarget = source.charTarget;
    const wordTarget = source.wordTarget;
    const includeFootnotes = source.includeFootnotes;
    const charactersIncludeSpaces = source.charactersIncludeSpaces;
    const excludeComments = source.excludeComments;
    let excludeTag = source.excludeTag;
    const includeBibliographyEstimate = source.includeBibliographyEstimate;
    const wordsPerCitation = source.wordsPerCitation;
    const charsPerCitation = source.charsPerCitation;
    const thousandSeperator = source.thousandSeperator;
    const useThousandSeperator = source.useThousandSeperator;
    const naChar = source.naChar;
    const subsectionStartChar = source.subsectionStartChar;
    const wordsPerPage = source.wordsPerPage;
    const pathToIndexFile = source.pathToIndexFile;

    let sourceTag = source.sourceTag;
    let excludeTag = source.excludeTag;
    // prepend hashtags for tags
    if (sourceTag) if (!sourceTag.startsWith("#")) sourceTag = "#" + sourceTag;
    if (excludeTag) if (!excludeTag.startsWith("#")) excludeTag = "#" + excludeTag;

    // const nameOfIndexFile = source.nameOfIndexFile; // used for Longform plugin, not implemented yet
    //----------------------------------------------------
    // Functions
    //----------------------------------------------------

    // < Functions
    function getWordCount(text) {
    // Regex from BetterWordCount Plugin
    const spaceDelimitedChars = /A-Za-z\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC/
    @@ -39,6 +44,7 @@ function getWordCount(text) {
    ].join("|"), "g");
    return (text.match(pattern) || []).length;
    }

    function getCharacterCount(text) {
    if (charactersIncludeSpaces) return text.length;
    return text.replaceAll(" ", "").length;
    @@ -63,8 +69,15 @@ function removeMarkdown (text) {
    .replace(/!?\[(.+)\]\(.+\)/g, "$1") // URLs & Image Captions
    .replace(/\*|_|\[\[|\]\]|\||==|~~|---|#|> |`/g, ""); // Markdown Syntax

    if (excludeComments) plaintext = plaintext.replace(/%%|<!--|-->/g, ""); // remove only comment syntax
    else plaintext = plaintext.replace(/<!--.*?-->/sg, "").replace(/%%.*?%%/sg, "");
    if (excludeComments) {
    plaintext = plaintext
    .replace(/<!--.*?-->/sg, "")
    .replace(/%%.*?%%/sg, "");
    }
    else {
    plaintext = plaintext
    .replace(/%%|<!--|-->/g, ""); // remove only comment syntax
    }

    return plaintext;
    }
    @@ -101,64 +114,89 @@ function toPercentStr (share) {
    return (share * 100).toFixed(1).toString() + "%";
    }

    // < table construction
    const getTableContents = () => {
    let totalWords = 0;
    let totalChars = 0;
    //----------------------------------------------------
    // Table Construction
    //----------------------------------------------------
    async function getTableContents () {
    const output = [];
    let completeText = "";
    let totalWords, totalChars = 0;
    let cumulativeShare = 0;
    let sectionCounter = 0;
    let subsectionCounter = 0;

    // get sections via folder or via tag
    let sections;
    if (sourceFolder) sections = dv.pages("\"" + sourceFolder + "\"");
    else sections = dv.pages(sourceTag);

    // < exclude sections with status "exclude"
    sections = sections.filter(t => t.status !== "exclude");
    numExcludeStatus = sections.filter(t => t.status === "exclude").length;
    // or exclude tag
    sections = sections.filter(t => t.status !== "exclude");
    if (excludeTag !== "") {
    numExcludedNotes = sections.filter(t => t.file.tags.includes(excludeTag)).length;
    sections = sections.filter(t => !t.file.tags.includes(excludeTag));
    }

    // < SORT sections
    sections = sections.sort(s => s.file.name);
    // SORT sections
    if (pathToIndexFile) {
    const draftName = sourceFolder.split("/").pop();
    const longformOrder =
    dv.page(pathToIndexFile)
    .drafts
    .filter(d => d.name === draftName)
    .scenes;

    sections = sections.sort(
    s => s.file.name,
    "desc",
    (a, b) => longformOrder.indexOf(b) - longformOrder.indexOf(a)
    );
    } else {
    sections = sections.sort(s => s.file.name);
    }

    // not yet implemented for Longform plugin
    // let indexPath = sourceFolder.split("/").slice(0, -2).join("/") + nameOfIndexFile;
    // if (indexPath.slice (-3) != ".md") indexPath += ".md";

    // < SECTIONS LOOP
    const sectionPaths = sections.file.path;
    for (let i = 0; i < sectionPaths.length; i++) {
    //-------------------------------------------------
    // SECTIONS LOOP
    //-------------------------------------------------
    for (const section of sections) {

    // < read page content via Obsidian API
    let content = "";
    const page = this.app.vault.getAbstractFileByPath(sectionPaths.values[i]);
    if (page.unsafeCachedData) content = page.unsafeCachedData;
    // read page content
    let content = await dv.io.load(section.file.path); // eslint-disable-line no-await-in-loop

    // < clean up
    // clean up
    content = removeMarkdown (content);
    if (!includeFootnotes) content = removeFootnotes (content);
    content = content
    .replace(/(^\s*)|(\s*$)/g, "") // remove the start and end spaces of the given string
    .replace(/ {2,}/g, " "); // reduce multiple spaces to a single space

    // < Table Values: Count & Share
    // Table Values: Count & Share
    const characterCount = getCharacterCount(content);
    const wordCount = getWordCount(content);
    cumulativeShare += calculateShare(characterCount, wordCount);

    // < Table Values: name & status
    const name = sections[i].file.link;
    let status = sections[i].status;
    // Status
    let status = section.status;
    if (!status) status = " ";

    // < push table values
    // Section numbering
    const isSubsection = section.file.name.startsWith(subsectionStartChar);
    let sectionNumbering;
    if (isSubsection) {
    subsectionCounter++;
    sectionNumbering = sectionCounter.toString() + "." + subsectionCounter.toString();
    }
    else {
    subsectionCounter = 0;
    sectionCounter++;
    sectionNumbering = sectionCounter.toString().strong();
    }

    // push table values
    output.push([
    name,
    sectionNumbering,
    section.file.link,
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    toPercentStr(cumulativeShare),
    @@ -171,10 +209,11 @@ const getTableContents = () => {
    if (includeBibliographyEstimate) completeText += content;
    }

    //-------------------------------------------------
    // OVERALL
    //-------------------------------------------------

    // < OVERALL

    // < Bibliography Estimate
    // Bibliography Estimate
    if (includeBibliographyEstimate) {
    const citationCount = countPandocCitations(completeText);
    const wordCount = citationCount * wordsPerCitation;
    @@ -183,6 +222,7 @@ const getTableContents = () => {
    cumulativeShare += calculateShare(characterCount, wordCount);

    output.push([
    "",
    "Bibliography (Estimate)",
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    @@ -195,42 +235,34 @@ const getTableContents = () => {
    totalWords += wordCount;
    }

    // < Totals calculation
    // Totals calculation
    const totalPages = (totalWords / wordsPerPage).toFixed(1);
    const totalProgress = calculateShare(totalChars, totalWords);
    let total = "Total";
    if (wordsPerPage) total += "&nbsp;&nbsp;&nbsp;&nbsp;(" + totalPages + " Pages)";
    output.push([
    "Total".strong(),
    "",
    total.strong(),
    insert1000sep(totalChars).strong(),
    insert1000sep(totalWords).strong(),
    toPercentStr(totalProgress).strong(),
    naChar.strong()
    ]);

    // Pages
    if (wordsPerPage) {
    const totalPages = (totalWords / wordsPerPage).toFixed(1);
    output.push([
    (totalPages.toString() + " Pages").strong(),
    "",
    "",
    "",
    ""
    ]);
    }

    // < Progress Bar
    // Target & Progress Bar
    const progressBar =
    "&nbsp;&nbsp;&nbsp;&nbsp;"
    + " <progress max=\"100\" value=\""
    + (totalProgress * 100).toFixed().toString()
    + "\"> </progress>";

    // < Target
    let charTargetText = naChar;
    let wordTargetText = naChar;
    if (charTarget) charTargetText = insert1000sep(charTarget);
    if (wordTarget) wordTargetText = insert1000sep(wordTarget);

    output.push([
    "",
    "Target".strong() + progressBar,
    charTargetText.strong(),
    wordTargetText.strong(),
    @@ -239,14 +271,19 @@ const getTableContents = () => {
    ]);

    return output;
    };
    }

    //----------------------------------------------------
    // Main
    //----------------------------------------------------

    // < Print Table
    // Print Table
    let numExcludedNotes = 0;
    let numExcludeStatus = 0;
    dv.table(["Section", "Chars", "Words", "Target", "Status"], getTableContents());
    const tcontent = await getTableContents();
    dv.table(["⟡", "Section", "Chars", "Words", "Target", "Status"], tcontent);

    // < Append Settings Info
    // Append Settings Info
    let settingFt = "Footnotes excluded. ";
    let settingExcludeTag = "";
    let settingExcludeStatus = "";
    3 changes: 2 additions & 1 deletion word-count-dashboard.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    ## Wordcount Dashboard for Obsidian
    ## Wordcount Dashboard for Obsidian Dataview

    <img src="https://user-images.githubusercontent.com/73286100/134819246-663fb637-138c-45db-b1c9-5fe681010213.png" alt="image" width=50%>

    @@ -9,6 +9,7 @@
    4. Insert the [dataviewjs-script](#file-dataviewjs_wordcount_obsidian-js) into the `dataviewjs`-codeblock
    5. [Enter the configuration values in the note template](#file-wordcount_dashboard_template-txt-L6). (The surrounding `%% %%` ensure that they are treated as comments, so the configuration will not be displayed in Preview Mode.)
    6. The status column will be populated with the value of the YAML-key `status` of every document, i.e., you have to use the add the following YAML-Header to every note.
    7. When also using the [Longform Plugin](https://github.com/kevboh/longform), you can name the index file in your settings and the Dashboard will automatically change order based on the order of scenes in your Draft.

    ```yaml
    ---
    25 changes: 11 additions & 14 deletions wordcount_dashboard.css
    Original file line number Diff line number Diff line change
    @@ -1,31 +1,28 @@
    /* used to properly align the numbers of the dataviewjs wordcount snippet
    https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99 */

    .wordcountTable table.dataview.table-view-table td,
    .wordcountTable table.dataview.table-view-table th {
    border-left: 2px solid;
    }
    .wordcountTable table.dataview.table-view-table td:first-child,
    .wordcountTable table.dataview.table-view-table th:first-child {
    border-left: none;
    padding: 4px 7px;
    border-left: none;
    text-align: center;
    }

    .wordcountTable table.dataview.table-view-table td:nth-child(2),
    .wordcountTable table.dataview.table-view-table td:nth-child(3),
    .wordcountTable table.dataview.table-view-table td:nth-child(4),
    .wordcountTable table.dataview.table-view-table td:nth-child(5){
    text-align: end;
    .wordcountTable table.dataview.table-view-table td:nth-child(5),
    .wordcountTable table.dataview.table-view-table td:nth-child(6) {
    text-align: end;
    }

    .wordcountTable table.dataview.table-view-table td:last-child{
    text-align: center;
    .wordcountTable table.dataview.table-view-table td:last-child {
    text-align: center;
    }

    /* full-width in Preview Mode*/
    .wordcountTable .markdown-preview-section {
    max-width: 100% !important;
    }

    .wordcountTable table.dataview.table-view-table th{
    text-align: center;
    }
    .wordcountTable table.dataview.table-view-table th {
    text-align: center;
    }
    39 changes: 26 additions & 13 deletions wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -3,35 +3,48 @@ cssclass: wordcountTable
    ---

    %%
    ## Configuration
    **Gets either notes in a folder or notes with a certain tag. Leave one of them empty.**
    sourceFolder:: Writing/Interdependence & Innovation/Drafts/Entwurf
    sourceTag:: #stories

    **set to 0 to ignore**
    ### Configuration
    __Notes to display__
    *Gets either notes in a folder or notes with a certain tag. Leave one of them empty.*
    sourceFolder:: Writing/Interdependence & Innovation/Drafts/Showcase
    sourceTag::

    __Notes to exclude__
    *Leave empty to disable.*
    excludeTag:: #exclude
    *(Notes with the yaml-key "status" and value "exclude" for that key are also excluded.)*

    __Counting Settings__
    *set to zero to ignore*
    charTarget:: 60000
    wordTarget:: 0
    wordsPerPage:: 0

    includeFootnotes:: true
    includeFootnotes:: false
    charactersIncludeSpaces:: true
    excludeComments:: true

    **Notes to exclude**
    Leave empty to disable. (Notes with the yaml-key "status" and value "exclude" for that key are also excluded)
    excludeTag:: #exclude

    **Bibliography estimate for Pandoc Citations**
    __Bibliography Estimate for Pandoc Citations__
    includeBibliographyEstimate:: true
    wordsPerCitation:: 22
    charsPerCitation:: 155

    **purely visual**
    thousandSeperator:: .
    __Longform Plugin__
    *Leave empty to sort alphabethically. Enter the path to the index file of a longform project to sections by their order in the longform plugin. (The sourceFolder further above has to be a Longform Drafts folder.)*
    pathToIndexFile::

    *begin a filename with this character and it will be treated as subsection*
    subsectionStartChar:: _

    __Purely visual__
    useThousandSeperator:: true
    thousandSeperator:: .
    naChar:: —

    %%


    ```dataviewjs
    <!-- put the above code in a codeblock like this-->
    ```
  12. @chrisgrieser chrisgrieser revised this gist Dec 8, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.4
    // version 1.4.1

    // Import configuration
    const source = dv.current();
  13. @chrisgrieser chrisgrieser revised this gist Dec 8, 2021. 1 changed file with 3 additions and 4 deletions.
    7 changes: 3 additions & 4 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -205,6 +205,7 @@ const getTableContents = () => {
    naChar.strong()
    ]);

    // Pages
    if (wordsPerPage) {
    const totalPages = (totalWords / wordsPerPage).toFixed(1);
    output.push([
    @@ -226,8 +227,8 @@ const getTableContents = () => {
    // < Target
    let charTargetText = naChar;
    let wordTargetText = naChar;
    if (!charTarget) charTargetText = insert1000sep(charTarget);
    if (!wordTarget) wordTargetText = insert1000sep(wordTarget);
    if (charTarget) charTargetText = insert1000sep(charTarget);
    if (wordTarget) wordTargetText = insert1000sep(wordTarget);

    output.push([
    "Target".strong() + progressBar,
    @@ -237,8 +238,6 @@ const getTableContents = () => {
    naChar.strong()
    ]);


    // < return array
    return output;
    };

  14. @chrisgrieser chrisgrieser revised this gist Dec 7, 2021. 2 changed files with 17 additions and 1 deletion.
    17 changes: 16 additions & 1 deletion dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.3.6
    // version 1.4

    // Import configuration
    const source = dv.current();
    @@ -18,6 +18,7 @@ const charsPerCitation = source.charsPerCitation;
    const thousandSeperator = source.thousandSeperator;
    const useThousandSeperator = source.useThousandSeperator;
    const naChar = source.naChar;
    const wordsPerPage = source.wordsPerPage;

    // prepend hashtags for tags
    if (sourceTag) if (!sourceTag.startsWith("#")) sourceTag = "#" + sourceTag;
    @@ -204,6 +205,17 @@ const getTableContents = () => {
    naChar.strong()
    ]);

    if (wordsPerPage) {
    const totalPages = (totalWords / wordsPerPage).toFixed(1);
    output.push([
    (totalPages.toString() + " Pages").strong(),
    "",
    "",
    "",
    ""
    ]);
    }

    // < Progress Bar
    const progressBar =
    "&nbsp;&nbsp;&nbsp;&nbsp;"
    @@ -242,11 +254,13 @@ let settingExcludeStatus = "";
    let settingBibliography = "";
    let settingCharSpaces = "Character Count includes Spaces. ";
    let settingComments = "Comments included. ";
    let settingPages = "";

    if (includeFootnotes) settingFt = "Footnotes included. ";
    if (!includeBibliographyEstimate) settingBibliography = "Bibliography excluded. ";
    if (!charactersIncludeSpaces) settingCharSpaces = "Character Count without Spaces. ";
    if (excludeComments) settingComments = "Comments excluded. ";
    if (wordsPerPage) settingPages = "Assuming " + wordsPerPage.toString() + " words per page. ";

    if (numExcludeStatus) {
    let plural = "s";
    @@ -279,5 +293,6 @@ dv.span(
    + settingBibliography
    + settingComments
    + settingCharSpaces
    + settingPages
    + "</small>"
    );
    1 change: 1 addition & 0 deletions wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -11,6 +11,7 @@ sourceTag:: #stories
    **set to 0 to ignore**
    charTarget:: 60000
    wordTarget:: 0
    wordsPerPage:: 0

    includeFootnotes:: true
    charactersIncludeSpaces:: true
  15. @chrisgrieser chrisgrieser revised this gist Dec 6, 2021. 2 changed files with 104 additions and 113 deletions.
    216 changes: 103 additions & 113 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,8 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.3.5
    // version 1.3.6

    //Import configuration
    // Import configuration
    const source = dv.current();
    const sourceFolder = source.sourceFolder;
    let sourceTag = source.sourceTag;
    @@ -16,162 +16,155 @@ const includeBibliographyEstimate = source.includeBibliographyEstimate;
    const wordsPerCitation = source.wordsPerCitation;
    const charsPerCitation = source.charsPerCitation;
    const thousandSeperator = source.thousandSeperator;
    const naChar = source.naChar;
    const useThousandSeperator = source.useThousandSeperator;
    const naChar = source.naChar;

    // prepend hashtags for tags
    if (sourceTag) if (!sourceTag.startsWith("#")) sourceTag = "#" + sourceTag;
    if (excludeTag) if (!excludeTag.startsWith("#")) excludeTag = "#" + excludeTag;

    //const nameOfIndexFile = source.nameOfIndexFile; // used for Longform plugin, not implemented yet
    // const nameOfIndexFile = source.nameOfIndexFile; // used for Longform plugin, not implemented yet

    // < Functions
    function getWordCount(text) {
    //Regex from BetterWordCount Plugin
    var spaceDelimitedChars = /A-Za-z\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC/
    // Regex from BetterWordCount Plugin
    const spaceDelimitedChars = /A-Za-z\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC/
    .source;
    var nonSpaceDelimitedWords = /[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u4E00-\u9FD5]{1}/
    const nonSpaceDelimitedWords = /[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u4E00-\u9FD5]{1}/
    .source;
    var pattern = new RegExp([
    const pattern = new RegExp([
    "(?:[0-9]+(?:(?:,|\\.)[0-9]+)*|[\\-" + spaceDelimitedChars + "])+",
    nonSpaceDelimitedWords,
    nonSpaceDelimitedWords
    ].join("|"), "g");
    return (text.match(pattern) || []).length;
    }
    function getCharacterCount(text) {
    if (charactersIncludeSpaces) return text.length;
    return text.replaceAll(" ","").length;
    return text.replaceAll(" ", "").length;
    }

    function insert1000sep (num){
    function insert1000sep (num) {
    let numText = String(num);
    if (num >= 10000) numText =
    numText.slice(0, -3) +
    thousandSeperator +
    numText.slice (-3);
    if (!useThousandSeperator) return numText;
    if (num >= 10000) numText = numText.slice(0, -3) + thousandSeperator + numText.slice (-3); // eslint-disable-line no-magic-numbers
    return numText;
    }

    String.prototype.strong = function () {
    if (this == " ") return " ";
    if (this === " ") return " ";
    return "**" + this + "**";
    };

    function removeMarkdown (text){
    function removeMarkdown (text) {
    let plaintext = text
    .replace(/`\$?=[^`]+`/g, "") //inline dataview
    .replace(/^---\n.*?\n---\n/s,"") //YAML Header
    .replace(/\!?\[(.+)\]\(.+\)/g,"$1") //URLs & Image Captions
    .replace(/\*|_|\[\[|\]\]|\||==|~~|---|#|> |`/g,""); //Markdown Syntax
    .replace(/`\$?=[^`]+`/g, "") // inline dataview
    .replace(/^---\n.*?\n---\n/s, "") // YAML Header
    .replace(/!?\[(.+)\]\(.+\)/g, "$1") // URLs & Image Captions
    .replace(/\*|_|\[\[|\]\]|\||==|~~|---|#|> |`/g, ""); // Markdown Syntax

    if (!excludeComments) plaintext = plaintext
    .replace(/<!--.*?-->/sg,"") //HTML comments
    .replace(/%%.*?%%/sg,""); //Obsidian comments
    else plaintext = plaintext
    .replace(/%%|<!--|-->/g,""); //remove only comment syntax
    if (excludeComments) plaintext = plaintext.replace(/%%|<!--|-->/g, ""); // remove only comment syntax
    else plaintext = plaintext.replace(/<!--.*?-->/sg, "").replace(/%%.*?%%/sg, "");

    return plaintext;
    }

    function removeFootnotes (text){
    return text.replace(/^\[\^\w+\]:.*$/gm,"") //footnote at the end
    .replace(/\[\^\w+\]/g,""); //footnote reference inline
    function removeFootnotes (text) {
    return text
    .replace(/^\[\^\w+\]:.*$/gm, "") // footnote at the end
    .replace(/\[\^\w+\]/g, ""); // footnote reference inline
    }

    function countPandocCitations (text){
    let citations = text.match(/@\w+(?=[,;\] ])/gi);
    if (citations == null) return 0;
    let uniqCitations = [...new Set(citations)]; //only unique citations
    function countPandocCitations (text) {
    const citations = text.match(/@\w+(?=[,;\] ])/gi);
    if (citations) return 0;
    const uniqCitations = [...new Set(citations)]; // only unique citations
    return uniqCitations.length;
    }

    function calculateShare (charCount, wordCount){
    function calculateShare (charCount, wordCount) {
    if (!wordTarget && !charTarget) return naChar;
    let countToUse = 0;
    let targetToUse = 0;
    if (charTarget != 0) {
    if (charTarget) {
    countToUse = charCount;
    targetToUse = charTarget;
    }
    else if (wordTarget != 0) {
    if (wordTarget) {
    countToUse = wordCount;
    targetToUse = wordTarget;
    }
    else {
    return naChar;
    }
    return (countToUse / targetToUse);
    }

    function toPercentStr (share){
    function toPercentStr (share) {
    return (share * 100).toFixed(1).toString() + "%";
    }

    // < table construction
    const getTableContents = () => {
    let totalWords = 0;
    let totalChars = 0;
    let output = [];
    const output = [];
    let completeText = "";
    let cumulativeShare = 0;

    // get sections via folder or via tag
    let sections;
    if (sourceFolder) sections = dv.pages('"' + sourceFolder + '"');
    if (sourceFolder) sections = dv.pages("\"" + sourceFolder + "\"");
    else sections = dv.pages(sourceTag);

    // < exclude sections with status "exclude"
    sections = sections.filter(t => t.status != "exclude");
    numExcludeStatus = sections.filter(t => t.status == "exclude").length;
    sections = sections.filter(t => t.status !== "exclude");
    numExcludeStatus = sections.filter(t => t.status === "exclude").length;
    // or exclude tag
    if (excludeTag != ""){
    numExcludedNotes = sections
    .filter(t => t.file.tags.includes(excludeTag))
    .length;
    sections = sections
    .filter(t => !t.file.tags.includes(excludeTag));
    if (excludeTag !== "") {
    numExcludedNotes = sections.filter(t => t.file.tags.includes(excludeTag)).length;
    sections = sections.filter(t => !t.file.tags.includes(excludeTag));
    }

    // < SORT sections
    sections = sections.sort(s => s.file.name);

    // not yet implemented for Longform plugin
    //let indexPath = sourceFolder.split("/").slice(0, -2).join("/") + nameOfIndexFile;
    //if (indexPath.slice (-3) != ".md") indexPath += ".md";
    // let indexPath = sourceFolder.split("/").slice(0, -2).join("/") + nameOfIndexFile;
    // if (indexPath.slice (-3) != ".md") indexPath += ".md";

    // < SECTIONS LOOP
    let sectionPaths = sections.file.path;
    for(let i = 0; i < sectionPaths.length; i++){
    const sectionPaths = sections.file.path;
    for (let i = 0; i < sectionPaths.length; i++) {

    // < read page content via Obsidian API
    let content = "";
    let page = this.app.vault.getAbstractFileByPath(sectionPaths.values[i]);
    if (page.unsafeCachedData != null) content = page.unsafeCachedData;
    const page = this.app.vault.getAbstractFileByPath(sectionPaths.values[i]);
    if (page.unsafeCachedData) content = page.unsafeCachedData;

    // < clean up
    content = removeMarkdown (content);
    if (!includeFootnotes) content = removeFootnotes (content);
    content = content.replace(/(^\s*)|(\s*$)/g,"") // remove the start and end spaces of the given string
    .replace(/ {2,}/g," "); // reduce multiple spaces to a single space
    content = content
    .replace(/(^\s*)|(\s*$)/g, "") // remove the start and end spaces of the given string
    .replace(/ {2,}/g, " "); // reduce multiple spaces to a single space

    // < Table Values: Count & Share
    let characterCount = getCharacterCount(content);
    let wordCount = getWordCount(content);
    const characterCount = getCharacterCount(content);
    const wordCount = getWordCount(content);
    cumulativeShare += calculateShare(characterCount, wordCount);

    // < Table Values: name & status
    let name = sections[i].file.link;
    const name = sections[i].file.link;
    let status = sections[i].status;
    if (status == null) status = " ";
    if (!status) status = " ";

    // < push table values
    output.push([
    name,
    name,
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    toPercentStr(cumulativeShare),
    status
    ]);

    //add to totals & bibligraphy calculation
    // add to totals & bibliography calculation
    totalChars += characterCount;
    totalWords += wordCount;
    if (includeBibliographyEstimate) completeText += content;
    @@ -181,54 +174,51 @@ const getTableContents = () => {
    // < OVERALL

    // < Bibliography Estimate
    if (includeBibliographyEstimate){
    let citationCount = countPandocCitations(completeText);
    let wordCount = citationCount * wordsPerCitation;
    if (includeBibliographyEstimate) {
    const citationCount = countPandocCitations(completeText);
    const wordCount = citationCount * wordsPerCitation;
    let characterCount = citationCount * charsPerCitation;
    if (!charactersIncludeSpaces) characterCount = citationCount * (charsPerCitation - 20);
    if (!charactersIncludeSpaces) characterCount = citationCount * (charsPerCitation - 20); // eslint-disable-line no-magic-numbers
    cumulativeShare += calculateShare(characterCount, wordCount);

    output.push([
    "Bibliography (Estimate)",
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    "Bibliography (Estimate)",
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    toPercentStr(cumulativeShare),
    naChar
    naChar
    ]);

    //add for Totals calculation
    // add for Totals calculation
    totalChars += characterCount;
    totalWords += wordCount;
    }

    // < Totals calculation
    let totalProgress = calculateShare(totalChars, totalWords);
    const totalProgress = calculateShare(totalChars, totalWords);
    output.push([
    "Total".strong(),
    "Total".strong(),
    insert1000sep(totalChars).strong(),
    insert1000sep(totalWords).strong(),
    toPercentStr(totalProgress).strong(),
    naChar.strong()
    ]);

    // < Progress Bar
    let progressBar =
    '&nbsp;&nbsp;&nbsp;&nbsp;'
    + ' <progress max="100" value="'
    const progressBar =
    "&nbsp;&nbsp;&nbsp;&nbsp;"
    + " <progress max=\"100\" value=\""
    + (totalProgress * 100).toFixed().toString()
    + '"> </progress>';
    + "\"> </progress>";

    // < Target
    let charTargetText = naChar;
    let wordTargetText = naChar;
    if (charTarget != 0) {
    charTargetText = insert1000sep(charTarget);
    }
    if (wordTarget != 0) {
    wordTargetText = insert1000sep(wordTarget);
    }
    if (!charTarget) charTargetText = insert1000sep(charTarget);
    if (!wordTarget) wordTargetText = insert1000sep(wordTarget);

    output.push([
    "Target".strong() + progressBar,
    "Target".strong() + progressBar,
    charTargetText.strong(),
    wordTargetText.strong(),
    naChar.strong(),
    @@ -246,33 +236,33 @@ let numExcludeStatus = 0;
    dv.table(["Section", "Chars", "Words", "Target", "Status"], getTableContents());

    // < Append Settings Info
    let setting_ft = "Footnotes excluded. ";
    let setting_excludeTag = "";
    let setting_excludeStatus = "";
    let setting_bibliography = "";
    let setting_charSpaces = "Character Count includes Spaces. ";
    let setting_comments = "Comments included. ";

    if (includeFootnotes) setting_ft = "Footnotes included. ";
    if (!includeBibliographyEstimate) setting_bibliography = "Bibliography excluded. ";
    if (!charactersIncludeSpaces) setting_charSpaces = "Character Count without Spaces. ";
    if (excludeComments) setting_comments = "Comments excluded. ";

    if (numExcludeStatus != 0) {
    let settingFt = "Footnotes excluded. ";
    let settingExcludeTag = "";
    let settingExcludeStatus = "";
    let settingBibliography = "";
    let settingCharSpaces = "Character Count includes Spaces. ";
    let settingComments = "Comments included. ";

    if (includeFootnotes) settingFt = "Footnotes included. ";
    if (!includeBibliographyEstimate) settingBibliography = "Bibliography excluded. ";
    if (!charactersIncludeSpaces) settingCharSpaces = "Character Count without Spaces. ";
    if (excludeComments) settingComments = "Comments excluded. ";

    if (numExcludeStatus) {
    let plural = "s";
    if (numExcludeStatus == 1) plural = "";
    let excludedQuery = "\"status: exclude\" path:(" + sourceFolder + ")";
    setting_excludeStatus =
    if (numExcludeStatus === 1) plural = "";
    const excludedQuery = "\"status: exclude\" path:(" + sourceFolder + ")";
    settingExcludeStatus =
    "[" + numExcludeStatus.toString() + " Section" + plural + "]"
    + "(obsidian://search?query=" + encodeURIComponent(excludedQuery) + ")"
    + " with the status \"exclude\" omitted. ";
    }

    if (numExcludedNotes != 0) {
    if (numExcludedNotes) {
    let plural = "s";
    if (numExcludedNotes == 1) plural = "";
    let excludedQuery = "tag:" + excludeTag + " path:(" + sourceFolder + ")";
    setting_excludeTag =
    if (numExcludedNotes === 1) plural = "";
    const excludedQuery = "tag:" + excludeTag + " path:(" + sourceFolder + ")";
    settingExcludeTag =
    "[" + numExcludedNotes.toString() + " Section" + plural + "]"
    + "(obsidian://search?query=" + encodeURIComponent(excludedQuery) + ")"
    + " tagged with " + excludeTag
    @@ -283,11 +273,11 @@ dv.span("---");
    dv.span(
    "<small>"
    + "Settings: ".strong()
    + setting_excludeStatus
    + setting_excludeTag
    + setting_ft
    + setting_bibliography
    + setting_comments
    + setting_charSpaces
    + settingExcludeStatus
    + settingExcludeTag
    + settingFt
    + settingBibliography
    + settingComments
    + settingCharSpaces
    + "</small>"
    );
    1 change: 1 addition & 0 deletions wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -27,6 +27,7 @@ charsPerCitation:: 155

    **purely visual**
    thousandSeperator:: .
    useThousandSeperator:: true
    naChar:: —
    %%

  16. @chrisgrieser chrisgrieser revised this gist Dec 6, 2021. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion word-count-dashboard.md
    Original file line number Diff line number Diff line change
    @@ -14,4 +14,7 @@
    ---
    status:
    ---
    ```
    ```

    ### Known Issues
    It seems that this dahsboard fails when amount of words / notes becomes too high. As far as I can tell, this is due to limitations by dataview/Obsidian, and one could only be tackled by a dedicated plugin for Word Counts. If you really want multi-note word counts, please request a plugin like that in the forum!
  17. @chrisgrieser chrisgrieser revised this gist Nov 21, 2021. 2 changed files with 3 additions and 3 deletions.
    5 changes: 3 additions & 2 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.3.4
    // version 1.3.5

    //Import configuration
    const source = dv.current();
    @@ -58,6 +58,7 @@ String.prototype.strong = function () {

    function removeMarkdown (text){
    let plaintext = text
    .replace(/`\$?=[^`]+`/g, "") //inline dataview
    .replace(/^---\n.*?\n---\n/s,"") //YAML Header
    .replace(/\!?\[(.+)\]\(.+\)/g,"$1") //URLs & Image Captions
    .replace(/\*|_|\[\[|\]\]|\||==|~~|---|#|> |`/g,""); //Markdown Syntax
    @@ -184,7 +185,7 @@ const getTableContents = () => {
    let citationCount = countPandocCitations(completeText);
    let wordCount = citationCount * wordsPerCitation;
    let characterCount = citationCount * charsPerCitation;
    if (!charactersIncludeSpaces) characterCount = citationCount * (charsPerReference - 20);
    if (!charactersIncludeSpaces) characterCount = citationCount * (charsPerCitation - 20);
    cumulativeShare += calculateShare(characterCount, wordCount);

    output.push([
    1 change: 0 additions & 1 deletion word-count-dashboard.md
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,6 @@

    <img src="https://user-images.githubusercontent.com/73286100/134819246-663fb637-138c-45db-b1c9-5fe681010213.png" alt="image" width=50%>


    ### Setup
    1. Install [dataview](https://github.com/blacksmithgu/obsidian-dataview)
    2. Install the [CSS file](#file-wordcount_dashboard-css) as [CSS snippet](https://help.obsidian.md/How+to/Add+custom+styles.md#Use+Themes+and/or+CSS+snippets).
  18. @chrisgrieser chrisgrieser revised this gist Nov 18, 2021. 1 changed file with 8 additions and 9 deletions.
    17 changes: 8 additions & 9 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.3.3
    // version 1.3.4

    //Import configuration
    const source = dv.current();
    @@ -101,7 +101,7 @@ function calculateShare (charCount, wordCount){
    }

    function toPercentStr (share){
    return (share * 100).toFixed(1).toString() + " %";
    return (share * 100).toFixed(1).toString() + "%";
    }

    // < table construction
    @@ -114,12 +114,12 @@ const getTableContents = () => {

    // get sections via folder or via tag
    let sections;
    if (sourceFolder.trim() != "") sections = dv.pages('"' + sourceFolder + '"');
    if (sourceFolder) sections = dv.pages('"' + sourceFolder + '"');
    else sections = dv.pages(sourceTag);

    // < exclude sections with status "exclude"
    sections = sections.where(t => t.status != "exclude");
    numExcludeStatus = sections.where(t => t.status == "exclude").length;
    sections = sections.filter(t => t.status != "exclude");
    numExcludeStatus = sections.filter(t => t.status == "exclude").length;
    // or exclude tag
    if (excludeTag != ""){
    numExcludedNotes = sections
    @@ -148,8 +148,8 @@ const getTableContents = () => {
    // < clean up
    content = removeMarkdown (content);
    if (!includeFootnotes) content = removeFootnotes (content);
    content = content.replace(/(^\s*)|(\s*$)/gi,"") // remove the start and end spaces of the given string
    .replace(/ {2,}/gi," "); // reduce multiple spaces to a single space
    content = content.replace(/(^\s*)|(\s*$)/g,"") // remove the start and end spaces of the given string
    .replace(/ {2,}/g," "); // reduce multiple spaces to a single space

    // < Table Values: Count & Share
    let characterCount = getCharacterCount(content);
    @@ -213,8 +213,7 @@ const getTableContents = () => {
    // < Progress Bar
    let progressBar =
    '&nbsp;&nbsp;&nbsp;&nbsp;'
    + " "
    + '<progress max="100" value="'
    + ' <progress max="100" value="'
    + (totalProgress * 100).toFixed().toString()
    + '"> </progress>';

  19. @chrisgrieser chrisgrieser revised this gist Nov 18, 2021. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -17,8 +17,7 @@ charactersIncludeSpaces:: true
    excludeComments:: true

    **Notes to exclude**
    Leave empty to disable.
    Notes with the yaml-key "status" and value "exclude" for that key are also excluded
    Leave empty to disable. (Notes with the yaml-key "status" and value "exclude" for that key are also excluded)
    excludeTag:: #exclude

    **Bibliography estimate for Pandoc Citations**
  20. @chrisgrieser chrisgrieser revised this gist Nov 18, 2021. 1 changed file with 13 additions and 7 deletions.
    20 changes: 13 additions & 7 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.3.2
    // version 1.3.3

    //Import configuration
    const source = dv.current();
    @@ -57,12 +57,18 @@ String.prototype.strong = function () {
    };

    function removeMarkdown (text){
    let plaintext = text.replace(/^---\n.*\n---\n/s,"") //YAML header
    .replace(/\!?\[(.+)\]\(.+\)/g,"") //URLs & Image Captions
    .replace(/\*|_|\[\[|\]\]|\||==|~~|---|#|>|`/ig,""); //markdown syntax
    if (!excludeComments) return plaintext;
    return plaintext.replace(/<!--.*-->/sg,"") //HTML comments
    .replace(/%%.*%%/sg,""); //Obsidian comments
    let plaintext = text
    .replace(/^---\n.*?\n---\n/s,"") //YAML Header
    .replace(/\!?\[(.+)\]\(.+\)/g,"$1") //URLs & Image Captions
    .replace(/\*|_|\[\[|\]\]|\||==|~~|---|#|> |`/g,""); //Markdown Syntax

    if (!excludeComments) plaintext = plaintext
    .replace(/<!--.*?-->/sg,"") //HTML comments
    .replace(/%%.*?%%/sg,""); //Obsidian comments
    else plaintext = plaintext
    .replace(/%%|<!--|-->/g,""); //remove only comment syntax

    return plaintext;
    }

    function removeFootnotes (text){
  21. @chrisgrieser chrisgrieser revised this gist Nov 18, 2021. No changes.
  22. @chrisgrieser chrisgrieser revised this gist Nov 18, 2021. 1 changed file with 7 additions and 4 deletions.
    11 changes: 7 additions & 4 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,25 +1,28 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.3.1
    // version 1.3.2

    //Import configuration
    const source = dv.current();
    const sourceFolder = source.sourceFolder;
    let sourceTag = source.sourceTag;
    if (!sourceTag.startsWith("#")) sourceTag = "#" + sourceTag;
    const charTarget = source.charTarget;
    const wordTarget = source.wordTarget;
    const includeFootnotes = source.includeFootnotes;
    const charactersIncludeSpaces = source.charactersIncludeSpaces;
    const excludeComments = source.excludeComments;
    let excludeTag = source.excludeTag;
    if (!excludeTag.startsWith("#")) excludeTag = "#" + excludeTag;
    const includeBibliographyEstimate = source.includeBibliographyEstimate;
    const wordsPerCitation = source.wordsPerCitation;
    const charsPerCitation = source.charsPerCitation;
    const thousandSeperator = source.thousandSeperator;
    const naChar = source.naChar;
    //const nameOfIndexFile = source.nameOfIndexFile; // leave empty when not using the Longform plugin

    // prepend hashtags for tags
    if (sourceTag) if (!sourceTag.startsWith("#")) sourceTag = "#" + sourceTag;
    if (excludeTag) if (!excludeTag.startsWith("#")) excludeTag = "#" + excludeTag;

    //const nameOfIndexFile = source.nameOfIndexFile; // used for Longform plugin, not implemented yet

    // < Functions
    function getWordCount(text) {
  23. @chrisgrieser chrisgrieser revised this gist Nov 17, 2021. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.3
    // version 1.3.1

    //Import configuration
    const source = dv.current();
    @@ -105,7 +105,7 @@ const getTableContents = () => {

    // get sections via folder or via tag
    let sections;
    if (sourceFolder.trim() == "") sections = dv.pages('"' + sourceFolder + '"');
    if (sourceFolder.trim() != "") sections = dv.pages('"' + sourceFolder + '"');
    else sections = dv.pages(sourceTag);

    // < exclude sections with status "exclude"
    @@ -124,7 +124,7 @@ const getTableContents = () => {
    sections = sections.sort(s => s.file.name);

    // not yet implemented for Longform plugin
    //let indexPath = folder.split("/").slice(0, -2).join("/") + nameOfIndexFile;
    //let indexPath = sourceFolder.split("/").slice(0, -2).join("/") + nameOfIndexFile;
    //if (indexPath.slice (-3) != ".md") indexPath += ".md";

    // < SECTIONS LOOP
    @@ -252,7 +252,7 @@ if (excludeComments) setting_comments = "Comments excluded. ";
    if (numExcludeStatus != 0) {
    let plural = "s";
    if (numExcludeStatus == 1) plural = "";
    let excludedQuery = "\"status: exclude\" path:(" + folder + ")";
    let excludedQuery = "\"status: exclude\" path:(" + sourceFolder + ")";
    setting_excludeStatus =
    "[" + numExcludeStatus.toString() + " Section" + plural + "]"
    + "(obsidian://search?query=" + encodeURIComponent(excludedQuery) + ")"
    @@ -262,7 +262,7 @@ if (numExcludeStatus != 0) {
    if (numExcludedNotes != 0) {
    let plural = "s";
    if (numExcludedNotes == 1) plural = "";
    let excludedQuery = "tag:" + excludeTag + " path:(" + folder + ")";
    let excludedQuery = "tag:" + excludeTag + " path:(" + sourceFolder + ")";
    setting_excludeTag =
    "[" + numExcludedNotes.toString() + " Section" + plural + "]"
    + "(obsidian://search?query=" + encodeURIComponent(excludedQuery) + ")"
  24. @chrisgrieser chrisgrieser revised this gist Nov 15, 2021. 1 changed file with 22 additions and 20 deletions.
    42 changes: 22 additions & 20 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,6 @@
    // Word Count Dashboard (Obsidian dataviewjs snippet)
    // by @pseudometa, https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99
    // version 1.3

    //Import configuration
    const source = dv.current();
    @@ -20,7 +21,7 @@ const thousandSeperator = source.thousandSeperator;
    const naChar = source.naChar;
    //const nameOfIndexFile = source.nameOfIndexFile; // leave empty when not using the Longform plugin

    // > Functions
    // < Functions
    function getWordCount(text) {
    //Regex from BetterWordCount Plugin
    var spaceDelimitedChars = /A-Za-z\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC/
    @@ -94,7 +95,7 @@ function toPercentStr (share){
    return (share * 100).toFixed(1).toString() + " %";
    }

    // > table construction
    // < table construction
    const getTableContents = () => {
    let totalWords = 0;
    let totalChars = 0;
    @@ -103,10 +104,11 @@ const getTableContents = () => {
    let cumulativeShare = 0;

    // get sections via folder or via tag
    let sections = dv.pages('"' + folder + '"');
    sections = dv.pages(sourceTag);
    let sections;
    if (sourceFolder.trim() == "") sections = dv.pages('"' + sourceFolder + '"');
    else sections = dv.pages(sourceTag);

    // > exclude sections with status "exclude"
    // < exclude sections with status "exclude"
    sections = sections.where(t => t.status != "exclude");
    numExcludeStatus = sections.where(t => t.status == "exclude").length;
    // or exclude tag
    @@ -118,39 +120,39 @@ const getTableContents = () => {
    .filter(t => !t.file.tags.includes(excludeTag));
    }

    // >> SORT sections
    // < SORT sections
    sections = sections.sort(s => s.file.name);

    // not yet implemented for Longform plugin
    //let indexPath = folder.split("/").slice(0, -2).join("/") + nameOfIndexFile;
    //if (indexPath.slice (-3) != ".md") indexPath += ".md";

    // > SECTIONS LOOP
    // < SECTIONS LOOP
    let sectionPaths = sections.file.path;
    for(let i = 0; i < sectionPaths.length; i++){

    // >> read page content via Obsidian API
    // < read page content via Obsidian API
    let content = "";
    let page = this.app.vault.getAbstractFileByPath(sectionPaths.values[i]);
    if (page.unsafeCachedData != null) content = page.unsafeCachedData;

    // >> clean up
    // < clean up
    content = removeMarkdown (content);
    if (!includeFootnotes) content = removeFootnotes (content);
    content = content.replace(/(^\s*)|(\s*$)/gi,"") // remove the start and end spaces of the given string
    .replace(/ {2,}/gi," "); // reduce multiple spaces to a single space

    // >> Table Values: Count & Share
    // < Table Values: Count & Share
    let characterCount = getCharacterCount(content);
    let wordCount = getWordCount(content);
    cumulativeShare += calculateShare(characterCount, wordCount);

    // >> Table Values: name & status
    // < Table Values: name & status
    let name = sections[i].file.link;
    let status = sections[i].status;
    if (status == null) status = " ";

    // >> push table values
    // < push table values
    output.push([
    name,
    insert1000sep(characterCount),
    @@ -166,9 +168,9 @@ const getTableContents = () => {
    }


    // > OVERALL
    // < OVERALL

    // >> Bibliography Estimate
    // < Bibliography Estimate
    if (includeBibliographyEstimate){
    let citationCount = countPandocCitations(completeText);
    let wordCount = citationCount * wordsPerCitation;
    @@ -189,7 +191,7 @@ const getTableContents = () => {
    totalWords += wordCount;
    }

    // >> Totals calculation
    // < Totals calculation
    let totalProgress = calculateShare(totalChars, totalWords);
    output.push([
    "Total".strong(),
    @@ -199,15 +201,15 @@ const getTableContents = () => {
    naChar.strong()
    ]);

    // >> Progress Bar
    // < Progress Bar
    let progressBar =
    '&nbsp;&nbsp;&nbsp;&nbsp;'
    + " "
    + '<progress max="100" value="'
    + (totalProgress * 100).toFixed().toString()
    + '"> </progress>';

    // >> Target
    // < Target
    let charTargetText = naChar;
    let wordTargetText = naChar;
    if (charTarget != 0) {
    @@ -225,16 +227,16 @@ const getTableContents = () => {
    ]);


    // >> return array
    // < return array
    return output;
    };

    // >> Print Table
    // < Print Table
    let numExcludedNotes = 0;
    let numExcludeStatus = 0;
    dv.table(["Section", "Chars", "Words", "Target", "Status"], getTableContents());

    // > Append Settings Info
    // < Append Settings Info
    let setting_ft = "Footnotes excluded. ";
    let setting_excludeTag = "";
    let setting_excludeStatus = "";
  25. @chrisgrieser chrisgrieser revised this gist Nov 15, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion word-count-dashboard.md
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    1. Install [dataview](https://github.com/blacksmithgu/obsidian-dataview)
    2. Install the [CSS file](#file-wordcount_dashboard-css) as [CSS snippet](https://help.obsidian.md/How+to/Add+custom+styles.md#Use+Themes+and/or+CSS+snippets).
    3. Create a note with the [markdown note template](#file-wordcount_dashboard_template-txt).
    4. Insert the [dataviewjs-script](#file-dataviewjs_wordcount-js) into the `dataviewjs`-codeblock
    4. Insert the [dataviewjs-script](#file-dataviewjs_wordcount_obsidian-js) into the `dataviewjs`-codeblock
    5. [Enter the configuration values in the note template](#file-wordcount_dashboard_template-txt-L6). (The surrounding `%% %%` ensure that they are treated as comments, so the configuration will not be displayed in Preview Mode.)
    6. The status column will be populated with the value of the YAML-key `status` of every document, i.e., you have to use the add the following YAML-Header to every note.

  26. @chrisgrieser chrisgrieser revised this gist Nov 15, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion word-count-dashboard.md
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@
    ### Setup
    1. Install [dataview](https://github.com/blacksmithgu/obsidian-dataview)
    2. Install the [CSS file](#file-wordcount_dashboard-css) as [CSS snippet](https://help.obsidian.md/How+to/Add+custom+styles.md#Use+Themes+and/or+CSS+snippets).
    3. Create a note with the [markdown note template](#file-wordcount_dashboard_template.txt).
    3. Create a note with the [markdown note template](#file-wordcount_dashboard_template-txt).
    4. Insert the [dataviewjs-script](#file-dataviewjs_wordcount-js) into the `dataviewjs`-codeblock
    5. [Enter the configuration values in the note template](#file-wordcount_dashboard_template-txt-L6). (The surrounding `%% %%` ensure that they are treated as comments, so the configuration will not be displayed in Preview Mode.)
    6. The status column will be populated with the value of the YAML-key `status` of every document, i.e., you have to use the add the following YAML-Header to every note.
  27. @chrisgrieser chrisgrieser revised this gist Nov 15, 2021. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions word-count-dashboard.md
    Original file line number Diff line number Diff line change
    @@ -5,10 +5,10 @@

    ### Setup
    1. Install [dataview](https://github.com/blacksmithgu/obsidian-dataview)
    2. Install the [CSS file](https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99#file-wordcount_dashboard-css) as [CSS snippet](https://help.obsidian.md/How+to/Add+custom+styles.md#Use+Themes+and/or+CSS+snippets).
    3. Create a note with the [markdown note template](https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99#file-wordcount_dashboard-css).
    4. Insert the [dataviewjs-script](https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99#file-dataviewjs_wordcount-js) into the `dataviewjs`-codeblock
    5. [Enter the configuration values in the note template](https://gist.github.com/chrisgrieser/ac16a80cdd9e8e0e84606cc24e35ad99#file-wordcount_dashboard_template-txt-L6). (The surrounding `%% %%` ensure that they are treated as comments, so the configuration will not be displayed in Preview Mode.)
    2. Install the [CSS file](#file-wordcount_dashboard-css) as [CSS snippet](https://help.obsidian.md/How+to/Add+custom+styles.md#Use+Themes+and/or+CSS+snippets).
    3. Create a note with the [markdown note template](#file-wordcount_dashboard_template.txt).
    4. Insert the [dataviewjs-script](#file-dataviewjs_wordcount-js) into the `dataviewjs`-codeblock
    5. [Enter the configuration values in the note template](#file-wordcount_dashboard_template-txt-L6). (The surrounding `%% %%` ensure that they are treated as comments, so the configuration will not be displayed in Preview Mode.)
    6. The status column will be populated with the value of the YAML-key `status` of every document, i.e., you have to use the add the following YAML-Header to every note.

    ```yaml
  28. @chrisgrieser chrisgrieser revised this gist Nov 15, 2021. 1 changed file with 11 additions and 6 deletions.
    17 changes: 11 additions & 6 deletions dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -3,13 +3,16 @@

    //Import configuration
    const source = dv.current();
    const folder = source.folderPath;
    const sourceFolder = source.sourceFolder;
    let sourceTag = source.sourceTag;
    if (!sourceTag.startsWith("#")) sourceTag = "#" + sourceTag;
    const charTarget = source.charTarget;
    const wordTarget = source.wordTarget;
    const includeFootnotes = source.includeFootnotes;
    const charactersIncludeSpaces = source.charactersIncludeSpaces;
    const excludeComments = source.excludeComments;
    const excludeTag = source.excludeTag;
    let excludeTag = source.excludeTag;
    if (!excludeTag.startsWith("#")) excludeTag = "#" + excludeTag;
    const includeBibliographyEstimate = source.includeBibliographyEstimate;
    const wordsPerCitation = source.wordsPerCitation;
    const charsPerCitation = source.charsPerCitation;
    @@ -99,12 +102,14 @@ const getTableContents = () => {
    let completeText = "";
    let cumulativeShare = 0;

    // > exclude sections with status "exclude"
    // get sections via folder or via tag
    let sections = dv.pages('"' + folder + '"');
    numExcludeStatus = sections.where(t => t.status == "exclude").length;
    sections = sections.where(t => t.status != "exclude");
    sections = dv.pages(sourceTag);

    // > exclude tag
    // > exclude sections with status "exclude"
    sections = sections.where(t => t.status != "exclude");
    numExcludeStatus = sections.where(t => t.status == "exclude").length;
    // or exclude tag
    if (excludeTag != ""){
    numExcludedNotes = sections
    .filter(t => t.file.tags.includes(excludeTag))
  29. @chrisgrieser chrisgrieser revised this gist Nov 15, 2021. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions wordcount_dashboard_template.txt
    Original file line number Diff line number Diff line change
    @@ -4,8 +4,9 @@ cssclass: wordcountTable

    %%
    ## Configuration
    **Folder containing Notes**
    folderPath:: Writing/Interdependence & Innovation/Drafts/Entwurf
    **Gets either notes in a folder or notes with a certain tag. Leave one of them empty.**
    sourceFolder:: Writing/Interdependence & Innovation/Drafts/Entwurf
    sourceTag:: #stories

    **set to 0 to ignore**
    charTarget:: 60000
  30. @chrisgrieser chrisgrieser revised this gist Sep 30, 2021. 2 changed files with 63 additions and 39 deletions.
    102 changes: 63 additions & 39 deletions dataviewjs_wordcount.js → dataviewjs_wordcount_obsidian.js
    Original file line number Diff line number Diff line change
    @@ -9,7 +9,7 @@ const wordTarget = source.wordTarget;
    const includeFootnotes = source.includeFootnotes;
    const charactersIncludeSpaces = source.charactersIncludeSpaces;
    const excludeComments = source.excludeComments;
    const excludeTag = source.excludeTag;
    const excludeTag = source.excludeTag;
    const includeBibliographyEstimate = source.includeBibliographyEstimate;
    const wordsPerCitation = source.wordsPerCitation;
    const charsPerCitation = source.charsPerCitation;
    @@ -37,7 +37,10 @@ function getCharacterCount(text) {

    function insert1000sep (num){
    let numText = String(num);
    if (num >= 10000) numText = numText.slice(0, -3) + thousandSeperator + numText.slice (-3);
    if (num >= 10000) numText =
    numText.slice(0, -3) +
    thousandSeperator +
    numText.slice (-3);
    return numText;
    }

    @@ -49,7 +52,7 @@ String.prototype.strong = function () {
    function removeMarkdown (text){
    let plaintext = text.replace(/^---\n.*\n---\n/s,"") //YAML header
    .replace(/\!?\[(.+)\]\(.+\)/g,"") //URLs & Image Captions
    .replace(/\*|_|\[\[|\]\]|---|#|>|`/ig,""); //markdown syntax
    .replace(/\*|_|\[\[|\]\]|\||==|~~|---|#|>|`/ig,""); //markdown syntax
    if (!excludeComments) return plaintext;
    return plaintext.replace(/<!--.*-->/sg,"") //HTML comments
    .replace(/%%.*%%/sg,""); //Obsidian comments
    @@ -81,15 +84,20 @@ function calculateShare (charCount, wordCount){
    else {
    return naChar;
    }
    return (countToUse / targetToUse * 100).toFixed(1).toString() + " %";
    return (countToUse / targetToUse);
    }

    function toPercentStr (share){
    return (share * 100).toFixed(1).toString() + " %";
    }

    // > table construction
    const getTableContents = () => {
    let TotalWords = 0;
    let TotalChars = 0;
    let totalWords = 0;
    let totalChars = 0;
    let output = [];
    let completeText = "";
    let cumulativeShare = 0;

    // > exclude sections with status "exclude"
    let sections = dv.pages('"' + folder + '"');
    @@ -98,12 +106,21 @@ const getTableContents = () => {

    // > exclude tag
    if (excludeTag != ""){
    numExcludedNotes = sections.where(t => t.file.tags.includes(excludeTag)).length;
    sections = sections.where(t => !t.file.tags.includes(excludeTag));
    numExcludedNotes = sections
    .filter(t => t.file.tags.includes(excludeTag))
    .length;
    sections = sections
    .filter(t => !t.file.tags.includes(excludeTag));
    }

    // >> SORT sections
    sections = sections.sort(s => s.file.name);

    // not yet implemented for Longform plugin
    //let indexPath = folder.split("/").slice(0, -2).join("/") + nameOfIndexFile;
    //if (indexPath.slice (-3) != ".md") indexPath += ".md";

    // > SECTIONS
    // > SECTIONS LOOP
    let sectionPaths = sections.file.path;
    for(let i = 0; i < sectionPaths.length; i++){

    @@ -115,39 +132,34 @@ const getTableContents = () => {
    // >> clean up
    content = removeMarkdown (content);
    if (!includeFootnotes) content = removeFootnotes (content);
    content = content.replace(/(^\\s\*)|(\\s\*$)/gi,"") // remove the start and end spaces of the given string
    .replace(/\[ \]{2,}/gi," ") // reduce multiple spaces to a single space
    .replace(/\\n /,"\\n"); // exclude a new line with a start spacing
    content = content.replace(/(^\s*)|(\s*$)/gi,"") // remove the start and end spaces of the given string
    .replace(/ {2,}/gi," "); // reduce multiple spaces to a single space

    // >> Count
    // >> Table Values: Count & Share
    let characterCount = getCharacterCount(content);
    let wordCount = getWordCount(content);
    cumulativeShare += calculateShare(characterCount, wordCount);

    // Share of Total/Target
    let shareTarget = calculateShare(characterCount, wordCount);

    // >> push table values
    // >> Table Values: name & status
    let name = sections[i].file.link;
    let status = sections[i].status;
    if (status == null) status = " ";
    output.push([name,

    // >> push table values
    output.push([
    name,
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    insert1000sep(shareTarget),
    toPercentStr(cumulativeShare),
    status
    ]);

    //add to totals & biblicraphy calculation
    TotalChars += characterCount;
    TotalWords += wordCount;
    //add to totals & bibligraphy calculation
    totalChars += characterCount;
    totalWords += wordCount;
    if (includeBibliographyEstimate) completeText += content;
    }
    // >> sort sections
    output.sort(); // now, so only sections are sorted

    // not yet implemented for Longform plugin
    //let indexPath = folder.split("/").slice(0, -2).join("/") + nameOfIndexFile;
    //if (indexPath.slice (-3) != ".md") indexPath += ".md";

    // > OVERALL

    @@ -156,29 +168,40 @@ const getTableContents = () => {
    let citationCount = countPandocCitations(completeText);
    let wordCount = citationCount * wordsPerCitation;
    let characterCount = citationCount * charsPerCitation;
    let shareTarget = calculateShare(characterCount, wordCount);
    if (!charactersIncludeSpaces) characterCount = citationCount * (charsPerReference - 20);
    output.push(["Bibliography (Estimate)",
    cumulativeShare += calculateShare(characterCount, wordCount);

    output.push([
    "Bibliography (Estimate)",
    insert1000sep(characterCount),
    insert1000sep(wordCount),
    insert1000sep(shareTarget),
    toPercentStr(cumulativeShare),
    naChar
    ]);

    //add for Totals calculation
    TotalChars += characterCount;
    TotalWords += wordCount;
    totalChars += characterCount;
    totalWords += wordCount;
    }

    // >> Totals calculation
    let totalProgress = calculateShare(TotalChars, TotalWords);
    output.push(["Total".strong(),
    insert1000sep(TotalChars).strong(),
    insert1000sep(TotalWords).strong(),
    insert1000sep(totalProgress).strong(),
    let totalProgress = calculateShare(totalChars, totalWords);
    output.push([
    "Total".strong(),
    insert1000sep(totalChars).strong(),
    insert1000sep(totalWords).strong(),
    toPercentStr(totalProgress).strong(),
    naChar.strong()
    ]);

    // >> Progress Bar
    let progressBar =
    '&nbsp;&nbsp;&nbsp;&nbsp;'
    + " "
    + '<progress max="100" value="'
    + (totalProgress * 100).toFixed().toString()
    + '"> </progress>';

    // >> Target
    let charTargetText = naChar;
    let wordTargetText = naChar;
    @@ -188,13 +211,15 @@ const getTableContents = () => {
    if (wordTarget != 0) {
    wordTargetText = insert1000sep(wordTarget);
    }
    output.push(["Target".strong(),
    output.push([
    "Target".strong() + progressBar,
    charTargetText.strong(),
    wordTargetText.strong(),
    naChar.strong(),
    naChar.strong()
    ]);


    // >> return array
    return output;
    };
    @@ -237,7 +262,6 @@ if (numExcludedNotes != 0) {
    + " tagged with " + excludeTag
    + " omitted. ";
    }

    dv.span("---");

    dv.span(
    File renamed without changes.