jekyll-wikilinks 0.0.2 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/jekyll-wikilinks/config.rb +107 -0
- data/lib/jekyll-wikilinks/patch/context.rb +19 -0
- data/lib/jekyll-wikilinks/patch/doc_manager.rb +82 -0
- data/lib/jekyll-wikilinks/patch/site.rb +12 -0
- data/lib/jekyll-wikilinks/plugins/converter.rb +53 -0
- data/lib/jekyll-wikilinks/plugins/filter.rb +73 -0
- data/lib/jekyll-wikilinks/plugins/generator.rb +41 -0
- data/lib/jekyll-wikilinks/util/link_index.rb +112 -0
- data/lib/jekyll-wikilinks/util/parser.rb +447 -0
- data/lib/jekyll-wikilinks/util/regex.rb +66 -0
- data/lib/jekyll-wikilinks/version.rb +5 -3
- data/lib/jekyll-wikilinks.rb +39 -57
- metadata +49 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e65a8cc36608b07333b7fe02a0f37f100117c7ed94d0b2e0fa500520eee6a2aa
|
4
|
+
data.tar.gz: 8d28d1930fedbddfd17d13c431f42043cdfb3333493929f78977d8da122c0647
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bae180a6824ea1a646d9103a0e6701814e1792275ae1037119db819b91fc35a9d01d8fc5c4b64ea8ba689f9b799309fd662ce66e001fd89e1e83eb1114257fba
|
7
|
+
data.tar.gz: d9327ed183e0d9ade35f23633a70f01157b388cdb627de5890d63cfd151a0bf070ddf92313b92ca385f187b360b96fbaaa770b2dfc29cf9a28fc4ffdb45caa57
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "jekyll"
|
3
|
+
|
4
|
+
module Jekyll
|
5
|
+
module WikiLinks
|
6
|
+
|
7
|
+
class PluginConfig
|
8
|
+
|
9
|
+
ATTR_KEY = "attributes"
|
10
|
+
CONFIG_KEY = "wikilinks"
|
11
|
+
ENABLED_KEY = "enabled"
|
12
|
+
EXCLUDE_KEY = "exclude"
|
13
|
+
# css-related
|
14
|
+
CSS_KEY = "css"
|
15
|
+
NAME_KEY = "name"
|
16
|
+
# names
|
17
|
+
## valid
|
18
|
+
WEB_KEY = "web"
|
19
|
+
WIKI_KEY = "wiki"
|
20
|
+
## invalid
|
21
|
+
INV_WIKI_KEY = "invalid_wiki"
|
22
|
+
# INV_WEB_KEY = "invalid_web"
|
23
|
+
## embed
|
24
|
+
EMBED_WRAPPER_KEY = "embed_wrapper"
|
25
|
+
EMBED_TITLE_KEY = "embed_title"
|
26
|
+
EMBED_CONTENT_KEY = "embed_content"
|
27
|
+
EMBED_LINK_KEY = "embed_wiki_link"
|
28
|
+
EMBED_IMG_WRAPPER_KEY = "embed_image_wrapper"
|
29
|
+
EMBED_IMG_KEY = "embed_image"
|
30
|
+
|
31
|
+
def initialize(config)
|
32
|
+
@config ||= config
|
33
|
+
self.old_config_warn()
|
34
|
+
Jekyll.logger.debug "Excluded jekyll types: ", option(EXCLUDE_KEY) unless disabled?
|
35
|
+
end
|
36
|
+
|
37
|
+
# util
|
38
|
+
|
39
|
+
def css_name(name_key)
|
40
|
+
return option_css_name(name_key) if option_css_name(name_key)
|
41
|
+
# valid
|
42
|
+
return "wiki-link" if name_key == WIKI_KEY
|
43
|
+
# invalid
|
44
|
+
return "invalid-wiki-link" if name_key == INV_WIKI_KEY
|
45
|
+
# return "invalid-web-link" if name_key == INV_WEB_KEY
|
46
|
+
# embeds
|
47
|
+
return "embed-wrapper" if name_key == EMBED_WRAPPER_KEY
|
48
|
+
return "embed-title" if name_key == EMBED_TITLE_KEY
|
49
|
+
return "embed-content" if name_key == EMBED_CONTENT_KEY
|
50
|
+
return "embed-wiki-link" if name_key == EMBED_LINK_KEY
|
51
|
+
# img
|
52
|
+
return "embed-image-wrapper" if name_key == EMBED_IMG_WRAPPER_KEY
|
53
|
+
return "embed-image" if name_key == EMBED_IMG_KEY
|
54
|
+
end
|
55
|
+
|
56
|
+
def disabled?
|
57
|
+
option(ENABLED_KEY) == false
|
58
|
+
end
|
59
|
+
|
60
|
+
def disabled_attributes?
|
61
|
+
option_attributes(ENABLED_KEY) == false
|
62
|
+
end
|
63
|
+
|
64
|
+
def exclude?(type)
|
65
|
+
return false unless option(EXCLUDE_KEY)
|
66
|
+
return option(EXCLUDE_KEY).include?(type.to_s)
|
67
|
+
end
|
68
|
+
|
69
|
+
# options
|
70
|
+
|
71
|
+
def option(key)
|
72
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY][key]
|
73
|
+
end
|
74
|
+
|
75
|
+
def option_attributes(key)
|
76
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY][ATTR_KEY] && @config[CONFIG_KEY][ATTR_KEY][key]
|
77
|
+
end
|
78
|
+
|
79
|
+
def option_css(key)
|
80
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY][CSS_KEY] && @config[CONFIG_KEY][CSS_KEY][key]
|
81
|
+
end
|
82
|
+
|
83
|
+
def option_css_name(key)
|
84
|
+
option_css(NAME_KEY) && @config[CONFIG_KEY][CSS_KEY][NAME_KEY][key]
|
85
|
+
end
|
86
|
+
|
87
|
+
# !! deprecated !!
|
88
|
+
|
89
|
+
def option_exist?(key)
|
90
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY].include?(key)
|
91
|
+
end
|
92
|
+
|
93
|
+
def old_config_warn()
|
94
|
+
if @config.include?("wikilinks_collection")
|
95
|
+
Jekyll.logger.warn "As of 0.0.3, 'wikilinks_collection' is no longer used for configs. jekyll-wikilinks will scan all markdown files by default. Check README for details."
|
96
|
+
end
|
97
|
+
if option_exist?("assets_rel_path")
|
98
|
+
Jekyll.logger.warn "As of 0.0.5, 'assets_rel_path' is now 'path'."
|
99
|
+
end
|
100
|
+
if @config.include?("d3_graph_data")
|
101
|
+
Jekyll.logger.warn "As of 0.0.6, 'd3_graph_data' and graph functionality have been moved to the 'jekyll-graph' plugin."
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "../util/regex"
|
3
|
+
|
4
|
+
module Jekyll
|
5
|
+
module WikiLinks
|
6
|
+
|
7
|
+
#
|
8
|
+
# this class is responsible for answering any questions
|
9
|
+
# related to jekyll markdown documents
|
10
|
+
# that are meant to be processed by the wikilinks plugin
|
11
|
+
#
|
12
|
+
class DocManager
|
13
|
+
CONVERTER_CLASS = Jekyll::Converters::Markdown
|
14
|
+
|
15
|
+
def initialize(site)
|
16
|
+
return if $wiki_conf.disabled?
|
17
|
+
|
18
|
+
markdown_converter = site.find_converter_instance(CONVERTER_CLASS)
|
19
|
+
# filter docs based on configs
|
20
|
+
docs = []
|
21
|
+
docs += site.pages if !$wiki_conf.exclude?(:pages)
|
22
|
+
docs += site.docs_to_write.filter { |d| !$wiki_conf.exclude?(d.type) }
|
23
|
+
@md_docs = docs.filter { |doc| markdown_converter.matches(doc.extname) }
|
24
|
+
if @md_docs.nil? || @md_docs.empty?
|
25
|
+
Jekyll.logger.debug("No documents to process.")
|
26
|
+
end
|
27
|
+
|
28
|
+
@static_files ||= site.static_files
|
29
|
+
end
|
30
|
+
|
31
|
+
# accessors
|
32
|
+
|
33
|
+
def all
|
34
|
+
return @md_docs
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_doc_by_fname(filename)
|
38
|
+
return nil if filename.nil? || @md_docs.size == 0
|
39
|
+
docs = @md_docs.select{ |d| File.basename(d.basename, File.extname(d.basename)) == filename }
|
40
|
+
return nil if docs.nil? || docs.size > 1
|
41
|
+
return docs[0]
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_doc_by_url(url)
|
45
|
+
return nil if url.nil? || @md_docs.size == 0
|
46
|
+
docs = @md_docs.select{ |d| d.url == url }
|
47
|
+
return nil if docs.nil? || docs.size > 1
|
48
|
+
return docs[0]
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_doc_content(filename)
|
52
|
+
return nil if filename.nil? || @md_docs.size == 0
|
53
|
+
docs = @md_docs.select{ |d| File.basename(d.basename, File.extname(d.basename)) == filename }
|
54
|
+
return docs[0].content if docs.size == 1
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_image_by_fname(filename)
|
59
|
+
return nil if filename.nil? || @static_files.size == 0 || !SUPPORTED_IMG_FORMATS.any?{ |ext| ext == File.extname(filename).downcase }
|
60
|
+
docs = @static_files.select{ |d| File.basename(d.relative_path) == filename }
|
61
|
+
return nil if docs.nil? || docs.size > 1
|
62
|
+
return docs[0]
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.doc_has_header?(doc, header)
|
66
|
+
return nil if header.nil?
|
67
|
+
# leading + trailing whitespace is ignored when matching headers
|
68
|
+
header_results = doc.content.scan(REGEX_ATX_HEADER).flatten.map { |htxt| htxt.strip }
|
69
|
+
setext_header_results = doc.content.scan(REGEX_SETEXT_HEADER).flatten.map { |htxt| htxt.strip }
|
70
|
+
return header_results.include?(header.strip) || setext_header_results.include?(header.strip)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.doc_has_block_id?(doc, block_id)
|
74
|
+
return nil if block_id.nil?
|
75
|
+
# leading + trailing whitespace is ignored when matching blocks
|
76
|
+
block_id_results = doc.content.scan(REGEX_BLOCK).flatten.map { |bid| bid.strip }
|
77
|
+
return block_id_results.include?(block_id)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "jekyll"
|
2
|
+
require "nokogiri"
|
3
|
+
|
4
|
+
module Jekyll
|
5
|
+
module WikiLinks
|
6
|
+
|
7
|
+
class WebLinkConverter < Jekyll::Converter
|
8
|
+
priority :low
|
9
|
+
|
10
|
+
# config
|
11
|
+
CSS_KEY = "css"
|
12
|
+
CONFIG_KEY = "wikilinks"
|
13
|
+
EXCLUDE_KEY = "exclude"
|
14
|
+
# link types
|
15
|
+
# WEB_KEY = "web"
|
16
|
+
# WIKIL_KEY = "wiki"
|
17
|
+
# INVALID_KEY = "invalid"
|
18
|
+
# WIKI_EMBED_KEY = "wiki_embed"
|
19
|
+
|
20
|
+
def matches(ext)
|
21
|
+
ext =~ /^\.md$/i
|
22
|
+
end
|
23
|
+
|
24
|
+
def output_ext(ext)
|
25
|
+
".html"
|
26
|
+
end
|
27
|
+
|
28
|
+
# add 'web-link' css class to links that aren't
|
29
|
+
# - wikilinks
|
30
|
+
# - contain an excluded css class
|
31
|
+
def convert(content)
|
32
|
+
excluded_classes = option_css(EXCLUDE_KEY)
|
33
|
+
if excluded_classes.nil? || excluded_classes.empty?
|
34
|
+
css_def = "a:not(.#{$wiki_conf.css_name("wiki")}):not(.#{$wiki_conf.css_name("embed_wiki_link")})"
|
35
|
+
else
|
36
|
+
css_def = "a:not(.#{$wiki_conf.css_name("wiki")}):not(.#{$wiki_conf.css_name("embed_wiki_link")}):not(.#{excluded_classes.join("):not(.")})"
|
37
|
+
end
|
38
|
+
parsed_content = Nokogiri::HTML::fragment(content)
|
39
|
+
parsed_content.css(css_def).each do |link|
|
40
|
+
link.add_class('web-link')
|
41
|
+
end
|
42
|
+
content = parsed_content.to_html
|
43
|
+
end
|
44
|
+
|
45
|
+
# config helpers
|
46
|
+
|
47
|
+
def option_css(key)
|
48
|
+
@config[CONFIG_KEY] && @config[CONFIG_KEY][CSS_KEY] && @config[CONFIG_KEY][CSS_KEY][key]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module WikiLinks
|
5
|
+
|
6
|
+
module TypeFilters
|
7
|
+
# 'links' accepts untyped links, typed links, and attributes; fore and back.
|
8
|
+
|
9
|
+
# usage: {% assign note_links = page.links | doc_type = "notes" %}
|
10
|
+
# TODO: if you simply filter links against specific jekyll types, this filter is completely unecessary...
|
11
|
+
# // looping through backlinks:
|
12
|
+
# {% assign note_links = site.notes | where: "url", backlink.url | first %}
|
13
|
+
def doc_type(links, doc_type)
|
14
|
+
Jekyll.logger.error("'links' should not be nil") if links.nil?
|
15
|
+
return "No doc type given" if doc_type.empty?
|
16
|
+
return [] if links.empty?
|
17
|
+
|
18
|
+
site = @context.registers[:site]
|
19
|
+
links.each do |l|
|
20
|
+
# links
|
21
|
+
if l.keys.include?('url')
|
22
|
+
doc = site.documents.select{ |d| d.url == l['url'] && d.type.to_s == doc_type.to_s }
|
23
|
+
if !doc.nil? && doc.size != 1
|
24
|
+
links.delete(l)
|
25
|
+
end
|
26
|
+
# attributes
|
27
|
+
elsif l.keys.include?('urls')
|
28
|
+
l['urls'].each do |lurl|
|
29
|
+
doc = site.documents.select{ |d| d.url == lurl && d.type.to_s == doc_type.to_s }
|
30
|
+
if !doc.nil? && doc.size != 1
|
31
|
+
links['urls'].delete(lurl)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
Jekyll.logge.error("In 'doc_type' filter, 'links' do not have 'url' or 'urls'")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
return links.uniq
|
39
|
+
end
|
40
|
+
|
41
|
+
# usage: {% assign author_links = page.links | rel_type = "author" %}
|
42
|
+
def rel_type(links, link_type)
|
43
|
+
Jekyll.logger.error("'links' should not be nil") if links.nil?
|
44
|
+
return "No link type given" if link_type.empty?
|
45
|
+
return [] if links.empty?
|
46
|
+
|
47
|
+
site = @context.registers[:site]
|
48
|
+
links.each do |l|
|
49
|
+
if l.keys.include?('url')
|
50
|
+
if l['type'].to_s == link_type.to_s
|
51
|
+
docs = site.documents.select{ |d| d.url == l['url'] }
|
52
|
+
if !doc.nil? && doc.size != 1
|
53
|
+
links.delete(l)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
elsif l.keys.include?('urls')
|
57
|
+
l['urls'].each do |lurl|
|
58
|
+
docs = site.documents.select{ |d| d.url == lurl }
|
59
|
+
if !doc.nil? && doc.size != 1
|
60
|
+
links['urls'].delete(lurl)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
else
|
64
|
+
Jekyll.logge.error("In 'rel_type' filter, 'links' do not have 'url' or 'urls'")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
return links.uniq
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "jekyll"
|
3
|
+
|
4
|
+
require_relative "../patch/context"
|
5
|
+
require_relative "../patch/doc_manager"
|
6
|
+
require_relative "../patch/site"
|
7
|
+
require_relative "../util/link_index"
|
8
|
+
require_relative "../util/parser"
|
9
|
+
require_relative "converter"
|
10
|
+
|
11
|
+
module Jekyll
|
12
|
+
module WikiLinks
|
13
|
+
|
14
|
+
class Generator < Jekyll::Generator
|
15
|
+
|
16
|
+
def generate(site)
|
17
|
+
return if $wiki_conf.disabled?
|
18
|
+
|
19
|
+
@site ||= site
|
20
|
+
@context ||= Jekyll::WikiLinks::Context.new(site)
|
21
|
+
|
22
|
+
# setup helper classes
|
23
|
+
@parser = Parser.new(@site)
|
24
|
+
@site.link_index = LinkIndex.new(@site)
|
25
|
+
|
26
|
+
@site.doc_mngr.all.each do |doc|
|
27
|
+
@parser.parse(doc.content)
|
28
|
+
@site.link_index.populate_forward(doc, @parser.wikilink_blocks, @parser.wikilink_inlines, @site.doc_mngr.all)
|
29
|
+
end
|
30
|
+
# wait until all docs are processed before assigning backward facing metadata,
|
31
|
+
# this ensures all attributed/backlinks are collected for assignment
|
32
|
+
@site.doc_mngr.all.each do |doc|
|
33
|
+
@site.link_index.populate_backward(doc, @site.doc_mngr.all)
|
34
|
+
@site.link_index.assign_metadata(doc)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require_relative 'regex'
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module WikiLinks
|
5
|
+
|
6
|
+
class LinkIndex
|
7
|
+
attr_reader :index
|
8
|
+
|
9
|
+
def initialize(site)
|
10
|
+
@baseurl = site.baseurl
|
11
|
+
@index = {}
|
12
|
+
site.doc_mngr.all.each do |doc|
|
13
|
+
@index[doc.url] = LinksInfo.new()
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def assign_metadata(doc)
|
18
|
+
doc.data['attributed'] = @index[doc.url].attributed.uniq
|
19
|
+
doc.data['attributes'] = @index[doc.url].attributes.uniq
|
20
|
+
doc.data['backlinks'] = @index[doc.url].backlinks.uniq
|
21
|
+
doc.data['forelinks'] = @index[doc.url].forelinks.uniq
|
22
|
+
doc.data['missing'] = @index[doc.url].missing.uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
def populate_forward(doc, wikilink_blocks, wikilink_inlines, md_docs)
|
26
|
+
# attributes - blocks
|
27
|
+
wikilink_blocks.each do |tlbl|
|
28
|
+
urls = []
|
29
|
+
tlbl.list_items.each do |bullet_type, filename|
|
30
|
+
attr_doc = md_docs.detect { |d| File.basename(d.basename, File.extname(d.basename)) == filename }
|
31
|
+
if !attr_doc.nil?
|
32
|
+
urls << attr_doc.url
|
33
|
+
end
|
34
|
+
end
|
35
|
+
if !urls.nil? && !urls.empty?
|
36
|
+
@index[doc.url].attributes << {
|
37
|
+
'type' => tlbl.link_type,
|
38
|
+
'urls' => urls,
|
39
|
+
}
|
40
|
+
else
|
41
|
+
Jekyll.logger.warn("No documents found for urls: #{urls}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
# forelinks - inlines
|
45
|
+
wikilink_inlines.each do |wlil|
|
46
|
+
link_doc = md_docs.detect { |d| File.basename(d.basename, File.extname(d.basename)) == wlil.filename }
|
47
|
+
if !link_doc.nil?
|
48
|
+
@index[doc.url].forelinks << {
|
49
|
+
'type' => wlil.link_type,
|
50
|
+
'url' => link_doc.url,
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# ...process missing links
|
55
|
+
doc.content.scan(REGEX_INVALID_WIKI_LINK).each do |m|
|
56
|
+
ltext = m[0]
|
57
|
+
@index[doc.url].missing << ltext
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def populate_backward(doc, md_docs)
|
62
|
+
md_docs.each do |doc_to_link|
|
63
|
+
# attributed
|
64
|
+
@index[doc_to_link.url].attributes.each do |al|
|
65
|
+
urls = al['urls'].map { |url| self.remove_baseurl(url) }
|
66
|
+
if urls.include?(doc.url)
|
67
|
+
target_attr = @index[doc.url].attributed.detect { |atr| atr['type'] == al['type']}
|
68
|
+
# add
|
69
|
+
if !target_attr.nil?
|
70
|
+
target_attr['urls'] << doc_to_link.url
|
71
|
+
# create new
|
72
|
+
else
|
73
|
+
urls = @index[doc.url].attributed.detect { |a| a['type'] == al['type'] }
|
74
|
+
@index[doc.url].attributed << {
|
75
|
+
'type' => al['type'],
|
76
|
+
'urls' => [ doc_to_link.url ],
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
# backlinks
|
82
|
+
@index[doc_to_link.url].forelinks.each do |l|
|
83
|
+
if self.remove_baseurl(l['url']) == doc.url
|
84
|
+
@index[doc.url].backlinks << {
|
85
|
+
'type' => l['type'],
|
86
|
+
'url' => doc_to_link.url,
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def remove_baseurl(url)
|
94
|
+
return url.gsub(@baseurl, '') if !@baseurl.nil?
|
95
|
+
return url
|
96
|
+
end
|
97
|
+
|
98
|
+
class LinksInfo
|
99
|
+
attr_accessor :attributes, :attributed, :backlinks, :forelinks, :missing
|
100
|
+
|
101
|
+
def initialize
|
102
|
+
@attributed = [] # block typed backlinks; { 'type' => str, 'urls' => [ str ] }
|
103
|
+
@attributes = [] # block typed forelinks; { 'type' => str, 'urls' => [ str ] }
|
104
|
+
@backlinks = [] # inline typed and basic backlinks; { 'type' => str, 'url' => str }
|
105
|
+
@forelinks = [] # inline typed and basic forelinks; { 'type' => str, 'url' => str }
|
106
|
+
@missing = [] # missing forelinks; [ str ]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,447 @@
|
|
1
|
+
require_relative "regex"
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module WikiLinks
|
5
|
+
|
6
|
+
# more of a "parser" than a parser
|
7
|
+
class Parser
|
8
|
+
attr_accessor :doc_manager, :markdown_converter, :wikilink_inlines, :wikilink_blocks
|
9
|
+
|
10
|
+
# Use Jekyll's native relative_url filter
|
11
|
+
include Jekyll::Filters::URLFilters
|
12
|
+
|
13
|
+
CONVERTER_CLASS = Jekyll::Converters::Markdown
|
14
|
+
|
15
|
+
def initialize(site)
|
16
|
+
@context ||= Jekyll::WikiLinks::Context.new(site)
|
17
|
+
@doc_manager ||= site.doc_mngr
|
18
|
+
@markdown_converter ||= site.find_converter_instance(CONVERTER_CLASS)
|
19
|
+
@wikilink_blocks, @wikilink_inlines = [], [], []
|
20
|
+
end
|
21
|
+
|
22
|
+
# parsing
|
23
|
+
|
24
|
+
def parse(doc_content)
|
25
|
+
@wikilink_blocks, @wikilink_inlines = [], [], []
|
26
|
+
if !$wiki_conf.disabled_attributes?
|
27
|
+
self.parse_block_singles(doc_content)
|
28
|
+
self.parse_block_lists_mkdn(doc_content)
|
29
|
+
self.parse_block_lists_comma(doc_content)
|
30
|
+
end
|
31
|
+
self.parse_inlines(doc_content)
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_block_singles(doc_content)
|
35
|
+
bullet_type = ""
|
36
|
+
typed_link_block_matches = doc_content.scan(REGEX_TYPED_LINK_BLOCK)
|
37
|
+
if !typed_link_block_matches.nil? && typed_link_block_matches.size != 0
|
38
|
+
typed_link_block_matches.each do |wl_match|
|
39
|
+
link_type = wl_match[0]
|
40
|
+
filename = wl_match[1]
|
41
|
+
typed_link_block_wikilink = WikiLinkBlock.new(
|
42
|
+
link_type,
|
43
|
+
bullet_type,
|
44
|
+
filename,
|
45
|
+
)
|
46
|
+
@wikilink_blocks << typed_link_block_wikilink
|
47
|
+
doc_content.gsub!(typed_link_block_wikilink.md_regex, "")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_block_lists_comma(doc_content)
|
53
|
+
processing_link_type = nil
|
54
|
+
processing_wikilink_list = nil
|
55
|
+
bullet_type = ","
|
56
|
+
typed_link_block_list_item_matches = doc_content.scan(REGEX_TYPED_LINK_BLOCK_LIST_COMMA)
|
57
|
+
if !typed_link_block_list_item_matches.nil? && typed_link_block_list_item_matches.size != 0
|
58
|
+
# Match 1
|
59
|
+
# link-type-txt link-type
|
60
|
+
# filename link
|
61
|
+
# 3. alink
|
62
|
+
# Match 2
|
63
|
+
# link-type-txt
|
64
|
+
# filename
|
65
|
+
# 3. blink
|
66
|
+
# Match 3
|
67
|
+
# link-type-txt
|
68
|
+
# filename
|
69
|
+
# 3. clink
|
70
|
+
typed_link_block_list_item_matches.each do |wl_match|
|
71
|
+
link_type = wl_match[0]
|
72
|
+
link_filename_1 = wl_match[1]
|
73
|
+
link_filename_2 = wl_match[2]
|
74
|
+
if !link_type.nil?
|
75
|
+
# process previous wikilink_list
|
76
|
+
if !processing_wikilink_list.nil? && processing_wikilink_list.has_items?
|
77
|
+
@wikilink_blocks << processing_wikilink_list
|
78
|
+
doc_content.gsub!(processing_wikilink_list.md_regex, "")
|
79
|
+
end
|
80
|
+
processing_link_type = link_type
|
81
|
+
processing_wikilink_list = WikiLinkBlock.new(processing_link_type, bullet_type, link_filename_1)
|
82
|
+
processing_wikilink_list.add_item(bullet_type, link_filename_2) if !link_filename_2.nil?
|
83
|
+
else
|
84
|
+
Jekyll.logger.error("'processing_wikilink_list' was nil") if processing_wikilink_list.nil?
|
85
|
+
processing_wikilink_list.add_item(bullet_type, link_filename_2)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
# process previous wikilink_list
|
89
|
+
if !processing_wikilink_list.nil? && processing_wikilink_list.has_items?
|
90
|
+
@wikilink_blocks << processing_wikilink_list
|
91
|
+
doc_content.gsub!(processing_wikilink_list.md_regex, "")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse_block_lists_mkdn(doc_content)
|
97
|
+
processing_link_type = nil
|
98
|
+
processing_wikilink_list = nil
|
99
|
+
bullet_type = nil
|
100
|
+
typed_link_block_list_item_matches = doc_content.scan(REGEX_TYPED_LINK_BLOCK_LIST_MKDN)
|
101
|
+
if !typed_link_block_list_item_matches.nil? && typed_link_block_list_item_matches.size != 0
|
102
|
+
# Match 1
|
103
|
+
# link-type-txt more-types
|
104
|
+
# bullet
|
105
|
+
# filename
|
106
|
+
# Match 2
|
107
|
+
# link-type-txt
|
108
|
+
# bullet *
|
109
|
+
# filename alink
|
110
|
+
# Match 3
|
111
|
+
# link-type-txt
|
112
|
+
# bullet *
|
113
|
+
# filename blink
|
114
|
+
# Match 4
|
115
|
+
# link-type-txt more-types
|
116
|
+
# bullet
|
117
|
+
# filename
|
118
|
+
# Match 5
|
119
|
+
# link-type-txt
|
120
|
+
# bullet +
|
121
|
+
# filename alink
|
122
|
+
# Match 6
|
123
|
+
# link-type-txt
|
124
|
+
# bullet +
|
125
|
+
# filename blink
|
126
|
+
typed_link_block_list_item_matches.each do |wl_match|
|
127
|
+
link_type = wl_match[0]
|
128
|
+
bullet_type = wl_match[1]
|
129
|
+
link_filename = wl_match[2]
|
130
|
+
if !link_type.nil?
|
131
|
+
# process previous wikilink_list
|
132
|
+
if !processing_wikilink_list.nil? && processing_wikilink_list.has_items?
|
133
|
+
@wikilink_blocks << processing_wikilink_list
|
134
|
+
doc_content.gsub!(processing_wikilink_list.md_regex, "")
|
135
|
+
end
|
136
|
+
processing_link_type = link_type
|
137
|
+
processing_wikilink_list = WikiLinkBlock.new(processing_link_type)
|
138
|
+
else
|
139
|
+
Jekyll.logger.error("'processing_wikilink_list' was nil") if processing_wikilink_list.nil?
|
140
|
+
processing_wikilink_list.add_item(bullet_type, link_filename)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
# process previous wikilink_list
|
144
|
+
if !processing_wikilink_list.nil? && processing_wikilink_list.has_items?
|
145
|
+
@wikilink_blocks << processing_wikilink_list
|
146
|
+
doc_content.gsub!(processing_wikilink_list.md_regex, "")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def parse_inlines(doc_content)
|
152
|
+
wikilink_matches = doc_content.scan(REGEX_WIKI_LINKS)
|
153
|
+
if !wikilink_matches.nil? && wikilink_matches.size != 0
|
154
|
+
wikilink_matches.each do |wl_match|
|
155
|
+
@wikilink_inlines << WikiLinkInline.new(
|
156
|
+
wl_match[0],
|
157
|
+
wl_match[1],
|
158
|
+
wl_match[2],
|
159
|
+
wl_match[3],
|
160
|
+
wl_match[4],
|
161
|
+
wl_match[5],
|
162
|
+
)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
# replace text
|
166
|
+
return if @wikilink_inlines.nil?
|
167
|
+
@wikilink_inlines.each do |wikilink|
|
168
|
+
doc_content.gsub!(
|
169
|
+
wikilink.md_link_regex,
|
170
|
+
self.build_html(wikilink)
|
171
|
+
)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# building/converting
|
176
|
+
|
177
|
+
def build_html_embed(title, content, url)
|
178
|
+
# multi-line for readability
|
179
|
+
return [
|
180
|
+
"<div class=\"#{$wiki_conf.css_name("embed_wrapper")}\">",
|
181
|
+
"<div class=\"#{$wiki_conf.css_name("embed_title")}\">",
|
182
|
+
"#{title}",
|
183
|
+
"</div>",
|
184
|
+
"<div class=\"#{$wiki_conf.css_name("embed_content")}\">",
|
185
|
+
"#{@markdown_converter.convert(content)}",
|
186
|
+
"</div>",
|
187
|
+
"<a class=\"#{$wiki_conf.css_name("embed_wiki_link")}\" href=\"#{url}\"></a>",
|
188
|
+
"</div>",
|
189
|
+
].join("\n").gsub!("\n", "")
|
190
|
+
end
|
191
|
+
|
192
|
+
def build_html_img_embed(static_doc, is_svg=false)
|
193
|
+
svg_content = ""
|
194
|
+
if is_svg
|
195
|
+
File.open(static_doc.path, "r") do |svg_img|
|
196
|
+
svg_content = svg_img.read
|
197
|
+
end
|
198
|
+
return "<p><span class=\"#{$wiki_conf.css_name("embed_image_wrapper")}\">#{svg_content}</span></p>"
|
199
|
+
else
|
200
|
+
return "<p><span class=\"#{$wiki_conf.css_name("embed_image_wrapper")}\"><img class=\"#{$wiki_conf.css_name("embed_image")}\" src=\"#{relative_url(static_doc.relative_path)}\"/></span></p>"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def build_html(wikilink)
|
205
|
+
if wikilink.is_img?
|
206
|
+
linked_static_doc = @doc_manager.get_image_by_fname(wikilink.filename)
|
207
|
+
if wikilink.embedded? && wikilink.is_img?
|
208
|
+
return build_html_img_embed(linked_static_doc, is_svg=wikilink.is_img_svg?)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
linked_doc = @doc_manager.get_doc_by_fname(wikilink.filename)
|
212
|
+
if !linked_doc.nil?
|
213
|
+
link_type = wikilink.typed? ? " typed #{wikilink.link_type}" : ""
|
214
|
+
|
215
|
+
# label
|
216
|
+
wikilink_inner_txt = wikilink.clean_label_txt if wikilink.labelled?
|
217
|
+
|
218
|
+
lnk_doc_rel_url = relative_url(linked_doc.url)
|
219
|
+
# TODO not sure about downcase
|
220
|
+
fname_inner_txt = linked_doc['title'].downcase if wikilink_inner_txt.nil?
|
221
|
+
|
222
|
+
link_lvl = wikilink.describe['level']
|
223
|
+
if (link_lvl == "file")
|
224
|
+
wikilink_inner_txt = "#{fname_inner_txt}" if wikilink_inner_txt.nil?
|
225
|
+
return build_html_embed(
|
226
|
+
linked_doc['title'],
|
227
|
+
@doc_manager.get_doc_content(wikilink.filename),
|
228
|
+
lnk_doc_rel_url
|
229
|
+
) if wikilink.embedded?
|
230
|
+
elsif (link_lvl == "header" && DocManager.doc_has_header?(linked_doc, wikilink.header_txt))
|
231
|
+
# from: https://github.com/jekyll/jekyll/blob/6855200ebda6c0e33f487da69e4e02ec3d8286b7/Rakefile#L74
|
232
|
+
lnk_doc_rel_url += "\#" + Jekyll::Utils.slugify(wikilink.header_txt)
|
233
|
+
wikilink_inner_txt = "#{fname_inner_txt} > #{wikilink.header_txt}" if wikilink_inner_txt.nil?
|
234
|
+
elsif (link_lvl == "block" && DocManager.doc_has_block_id?(linked_doc, wikilink.block_id))
|
235
|
+
lnk_doc_rel_url += "\#" + wikilink.block_id.downcase
|
236
|
+
wikilink_inner_txt = "#{fname_inner_txt} > ^#{wikilink.block_id}" if wikilink_inner_txt.nil?
|
237
|
+
else
|
238
|
+
return '<span class="' + $wiki_conf.css_name("invalid_wiki") + '">' + wikilink.md_link_str + '</span>'
|
239
|
+
end
|
240
|
+
return '<a class="' + $wiki_conf.css_name("wiki") + link_type + '" href="' + lnk_doc_rel_url + '">' + wikilink_inner_txt + '</a>'
|
241
|
+
else
|
242
|
+
return '<span class="' + $wiki_conf.css_name("invalid_wiki") + '">' + wikilink.md_link_str + '</span>'
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# validation
|
248
|
+
|
249
|
+
def has_target_attr?(attribute)
|
250
|
+
attribute.list_item.each do |li|
|
251
|
+
return false if @doc_manager.get_doc_by_fname(li[1]).nil?
|
252
|
+
end
|
253
|
+
return true
|
254
|
+
end
|
255
|
+
|
256
|
+
def has_target_wl?(wikilink)
|
257
|
+
level = wikilink.describe['level']
|
258
|
+
linked_doc = @doc_manager.get_doc_by_fname(wikilink.filename)
|
259
|
+
return false if linked_doc.nil?
|
260
|
+
return false if level == "header" && !DocManager.doc_has_header?(linked_doc, wikilink.header_txt)
|
261
|
+
return false if level == "block" && !DocManager.doc_has_block_id?(linked_doc, wikilink.block_id)
|
262
|
+
return true
|
263
|
+
end
|
264
|
+
|
265
|
+
# wikilinks
|
266
|
+
|
267
|
+
class WikiLinkBlock
|
268
|
+
attr_accessor :link_type, :list_items
|
269
|
+
|
270
|
+
# parameters ordered by appearance in regex
|
271
|
+
def initialize(link_type, bullet_type=nil, filename=nil)
|
272
|
+
@link_type ||= link_type
|
273
|
+
@list_items = [] # li[0] = bullet_type; li[1] = filename
|
274
|
+
@list_items << [ bullet_type, filename ] if !bullet_type.nil? && !filename.nil?
|
275
|
+
end
|
276
|
+
|
277
|
+
def add_item(bullet_type, filename)
|
278
|
+
return if bullet_type.nil? || bullet_type.empty? || filename.nil? || filename.empty?
|
279
|
+
@list_items << [ bullet_type, filename ]
|
280
|
+
end
|
281
|
+
|
282
|
+
def md_regex
|
283
|
+
if typed? && has_items?
|
284
|
+
# single
|
285
|
+
if bullet_type?.empty?
|
286
|
+
link_type = %r{#{@link_type}#{REGEX_LINK_TYPE}}
|
287
|
+
list_item_strs = @list_items.map { |li| /#{REGEX_LINK_LEFT}#{li[1]}#{REGEX_LINK_RIGHT}\n/i }
|
288
|
+
md_link_regex = /#{link_type}#{list_item_strs.join("")}/i
|
289
|
+
# list (comma)
|
290
|
+
elsif bullet_type? == ","
|
291
|
+
tmp_list_items = @list_items.dup
|
292
|
+
first_item = tmp_list_items.shift()
|
293
|
+
link_type = /#{@link_type}#{REGEX_LINK_TYPE}#{REGEX_LINK_LEFT}#{first_item[1]}#{REGEX_LINK_RIGHT}\s*/i
|
294
|
+
list_item_strs = tmp_list_items.map { |li| /#{li[0]}\s*#{REGEX_LINK_LEFT}#{li[1]}#{REGEX_LINK_RIGHT}\s*/i }
|
295
|
+
md_link_regex = /#{link_type}#{list_item_strs.join('')}/i
|
296
|
+
# list (md)
|
297
|
+
elsif !bullet_type?.match(REGEX_BULLET).nil?
|
298
|
+
link_type = %r{#{@link_type}#{REGEX_LINK_TYPE}\n}
|
299
|
+
list_item_strs = @list_items.map { |li| /#{Regexp.escape(li[0])}\s#{REGEX_LINK_LEFT}#{li[1]}#{REGEX_LINK_RIGHT}\n/i }
|
300
|
+
md_link_regex = /#{link_type}#{list_item_strs.join("")}/i
|
301
|
+
else
|
302
|
+
Jekyll.logger.error("bullet_types not uniform or invalid: #{bullet_type?}")
|
303
|
+
end
|
304
|
+
return md_link_regex
|
305
|
+
else
|
306
|
+
Jekyll.logger.error("WikiLinkBlockList.md_regex error")
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def md_str
|
311
|
+
if typed? && has_items?
|
312
|
+
if bullet_type? == ","
|
313
|
+
link_type = "#{@link_type}::"
|
314
|
+
list_item_strs = @list_items.map { |li| "\[\[#{li[1]}\]\]#{li[0]}" }
|
315
|
+
md_link_str = (link_type + list_item_strs.join('')).delete_suffix(",")
|
316
|
+
elsif "+*-".include?(bullet_type?)
|
317
|
+
link_type = "#{@link_type}::\n"
|
318
|
+
list_item_strs = @list_items.map { |li| li[0] + " \[\[#{li[1]}\]\]\n" }
|
319
|
+
md_link_str = link_type + list_item_strs.join('')
|
320
|
+
else
|
321
|
+
Jekyll.logger.error("Not a valid bullet_type: #{bullet_type?}")
|
322
|
+
end
|
323
|
+
return md_link_str
|
324
|
+
else
|
325
|
+
Jekyll.logger.error("WikiLinkBlockList.md_str error")
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def bullet_type?
|
330
|
+
bullets = @list_items.map { |li| li[0] }
|
331
|
+
return bullets.uniq.first if bullets.uniq.size == 1
|
332
|
+
end
|
333
|
+
|
334
|
+
def has_items?
|
335
|
+
return !@list_items.nil? && !@list_items.empty?
|
336
|
+
end
|
337
|
+
|
338
|
+
def typed?
|
339
|
+
return !@link_type.nil? && !@link_type.empty?
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# the wikilink class knows everything about the original markdown syntax and its semantic meaning
|
344
|
+
class WikiLinkInline
|
345
|
+
attr_accessor :embed, :link_type, :filename, :header_txt, :block_id, :label_txt
|
346
|
+
|
347
|
+
FILENAME = "filename"
|
348
|
+
HEADER_TXT = "header_txt"
|
349
|
+
BLOCK_ID = "block_id"
|
350
|
+
|
351
|
+
# parameters ordered by appearance in regex
|
352
|
+
def initialize(embed, link_type, filename, header_txt, block_id, label_txt)
|
353
|
+
@embed ||= embed
|
354
|
+
@link_type ||= link_type
|
355
|
+
@filename ||= filename
|
356
|
+
@header_txt ||= header_txt
|
357
|
+
@block_id ||= block_id
|
358
|
+
@label_txt ||= label_txt
|
359
|
+
end
|
360
|
+
|
361
|
+
# labels are really flexible, so we need to handle them with a bit more care
|
362
|
+
def clean_label_txt
|
363
|
+
return @label_txt.sub("[", "\\[").sub("]", "\\]")
|
364
|
+
end
|
365
|
+
|
366
|
+
# TODO: remove this once parsing is migrated to nokogiri...?
|
367
|
+
def md_link_str
|
368
|
+
embed = embedded? ? "!" : ""
|
369
|
+
link_type = typed? ? "#{@link_type}::" : ""
|
370
|
+
filename = described?(FILENAME) ? @filename : ""
|
371
|
+
if described?(HEADER_TXT)
|
372
|
+
header = "\##{@header_txt}"
|
373
|
+
block = ""
|
374
|
+
elsif described?(BLOCK_ID)
|
375
|
+
header = ""
|
376
|
+
block = "\#\^#{@block_id}"
|
377
|
+
elsif !described?(FILENAME)
|
378
|
+
Jekyll.logger.error "Invalid link level in 'md_link_str'. See WikiLink's 'md_link_str' for details"
|
379
|
+
end
|
380
|
+
label_ = labelled? ? "\|#{@label_txt}" : ""
|
381
|
+
return "#{embed}#{link_type}\[\[#{filename}#{header}#{block}#{label_}\]\]"
|
382
|
+
end
|
383
|
+
|
384
|
+
def md_link_regex
|
385
|
+
regex_embed = embedded? ? REGEX_LINK_EMBED : %r{}
|
386
|
+
regex_link_type = typed? ? %r{#{@link_type}#{REGEX_LINK_TYPE}} : %r{}
|
387
|
+
filename = described?(FILENAME) ? @filename : ""
|
388
|
+
if described?(HEADER_TXT)
|
389
|
+
header = %r{#{REGEX_LINK_HEADER}#{@header_txt}}
|
390
|
+
block = %r{}
|
391
|
+
elsif described?(BLOCK_ID)
|
392
|
+
header = %r{}
|
393
|
+
block = %r{#{REGEX_LINK_BLOCK}#{@block_id}}
|
394
|
+
elsif !described?(FILENAME)
|
395
|
+
Jekyll.logger.error "Invalid link level in regex. See WikiLink's 'md_link_regex' for details"
|
396
|
+
end
|
397
|
+
label_ = labelled? ? %r{#{REGEX_LINK_LABEL}#{clean_label_txt}} : %r{}
|
398
|
+
return %r{#{regex_embed}#{regex_link_type}#{REGEX_LINK_LEFT}#{filename}#{header}#{block}#{label_}#{REGEX_LINK_RIGHT}}
|
399
|
+
end
|
400
|
+
|
401
|
+
def describe
|
402
|
+
return {
|
403
|
+
'level' => level,
|
404
|
+
'labelled' => labelled?,
|
405
|
+
'embedded' => embedded?,
|
406
|
+
'typed_link' => typed?,
|
407
|
+
}
|
408
|
+
end
|
409
|
+
|
410
|
+
def labelled?
|
411
|
+
return !@label_txt.nil? && !@label_txt.empty?
|
412
|
+
end
|
413
|
+
|
414
|
+
def typed?
|
415
|
+
return !@link_type.nil? && !@link_type.empty?
|
416
|
+
end
|
417
|
+
|
418
|
+
def embedded?
|
419
|
+
return !@embed.nil? && @embed == "!"
|
420
|
+
end
|
421
|
+
|
422
|
+
def is_img?
|
423
|
+
# github supported image formats: https://docs.github.com/en/github/managing-files-in-a-repository/working-with-non-code-files/rendering-and-diffing-images
|
424
|
+
return SUPPORTED_IMG_FORMATS.any?{ |ext| ext == File.extname(@filename).downcase }
|
425
|
+
end
|
426
|
+
|
427
|
+
def is_img_svg?
|
428
|
+
return File.extname(@filename).downcase == ".svg"
|
429
|
+
end
|
430
|
+
|
431
|
+
def described?(chunk)
|
432
|
+
return (!@filename.nil? && !@filename.empty?) if chunk == FILENAME
|
433
|
+
return (!@header_txt.nil? && !@header_txt.empty?) if chunk == HEADER_TXT
|
434
|
+
return (!@block_id.nil? && !@block_id.empty?) if chunk == BLOCK_ID
|
435
|
+
Jekyll.logger.error "There is no link level '#{chunk}' in WikiLink Struct"
|
436
|
+
end
|
437
|
+
|
438
|
+
def level
|
439
|
+
return "file" if described?(FILENAME) && !described?(HEADER_TXT) && !described?(BLOCK_ID)
|
440
|
+
return "header" if described?(FILENAME) && described?(HEADER_TXT) && !described?(BLOCK_ID)
|
441
|
+
return "block" if described?(FILENAME) && !described?(HEADER_TXT) && described?(BLOCK_ID)
|
442
|
+
return "invalid"
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
end
|
447
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# regex.rb
|
2
|
+
# regex constants defining supported file types and valid names for files, variables, or text
|
3
|
+
#
|
4
|
+
|
5
|
+
module Jekyll
|
6
|
+
module WikiLinks
|
7
|
+
# <regex_variables> only work with 'match' function, not with 'scan' function. :/
|
8
|
+
# oh well...they are there for easier debugging...
|
9
|
+
|
10
|
+
# supported image formats
|
11
|
+
# from: https://docs.github.com/en/github/managing-files-in-a-repository/working-with-non-code-files/rendering-and-diffing-images
|
12
|
+
SUPPORTED_IMG_FORMATS = Set.new(['.png', '.jpg', '.gif', '.psd', '.svg'])
|
13
|
+
|
14
|
+
# wikilink constants
|
15
|
+
REGEX_LINK_LEFT = /\[\[/
|
16
|
+
REGEX_LINK_RIGHT = /\]\]/
|
17
|
+
REGEX_LINK_EMBED = /(?<embed>(\!))/
|
18
|
+
REGEX_LINK_TYPE = /\s*::\s*/
|
19
|
+
REGEX_LINK_HEADER = /\#/
|
20
|
+
REGEX_LINK_BLOCK = /\#\^/
|
21
|
+
REGEX_LINK_LABEL = /\|/
|
22
|
+
|
23
|
+
# wikilink usable char requirements
|
24
|
+
REGEX_LINK_TYPE_TXT = /(?<link-type-txt>([^\n\s\!\#\^\|\]]+))/i
|
25
|
+
REGEX_FILENAME = /(?<filename>([^\\\/:\#\^\|\[\]]+))/i
|
26
|
+
REGEX_HEADER_TXT = /(?<header-txt>([^\!\#\^\|\[\]]+))/i
|
27
|
+
REGEX_BLOCK_ID_TXT = /(?<block-id>([^\\\/:\!\#\^\|\[\]^\n]+))/i
|
28
|
+
REGEX_LABEL_TXT = /(?<label-txt>((.+?)(?=\]\])))/i
|
29
|
+
|
30
|
+
# target markdown text (headers, lists, and blocks)
|
31
|
+
## kramdown regexes
|
32
|
+
### atx header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rb#L29
|
33
|
+
REGEX_ATX_HEADER = /^\#{1,6}[\t ]*([^ \t].*)\n/i
|
34
|
+
### setext header: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/header.rb#L17
|
35
|
+
REGEX_SETEXT_HEADER = /^ {0,3}([^ \t].*)\n[-=][-=]*[ \t\r\f\v]*\n/i
|
36
|
+
## list item: https://github.com/gettalong/kramdown/blob/master/lib/kramdown/parser/kramdown/list.rb#L49
|
37
|
+
REGEX_BULLET = /(?<bullet>[+*-])/i
|
38
|
+
# REGEX_LIST_ITEM = /(^ {0,3}[+*-])([\t| ].*?\n)/i
|
39
|
+
REGEX_LIST_ITEM = /(^ {0,3}#{REGEX_BULLET})(\s(?:#{REGEX_LINK_LEFT}#{REGEX_FILENAME}#{REGEX_LINK_RIGHT}))/i
|
40
|
+
## new-markdown-style
|
41
|
+
REGEX_BLOCK = /.*\s\^#{REGEX_BLOCK_ID_TXT}/i
|
42
|
+
|
43
|
+
# wikilinks
|
44
|
+
## inline
|
45
|
+
REGEX_WIKI_LINKS = %r{ # capture indeces
|
46
|
+
(#{REGEX_LINK_EMBED})? # 0
|
47
|
+
(#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE})? # 1
|
48
|
+
#{REGEX_LINK_LEFT}
|
49
|
+
#{REGEX_FILENAME} # 2
|
50
|
+
(#{REGEX_LINK_HEADER}#{REGEX_HEADER_TXT})? # 3
|
51
|
+
(#{REGEX_LINK_BLOCK}#{REGEX_BLOCK_ID_TXT})? # 4
|
52
|
+
(#{REGEX_LINK_LABEL}#{REGEX_LABEL_TXT})? # 5
|
53
|
+
#{REGEX_LINK_RIGHT}
|
54
|
+
}x
|
55
|
+
## block
|
56
|
+
REGEX_TYPED_LINK_BLOCK = /#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE}#{REGEX_LINK_LEFT}#{REGEX_FILENAME}#{REGEX_LINK_RIGHT}\n/i
|
57
|
+
# TODO: keep an eye on this -- using REGEX_FILENAME in two places
|
58
|
+
REGEX_TYPED_LINK_BLOCK_LIST_COMMA = /(?:#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE}\s*(?:#{REGEX_LINK_LEFT}#{REGEX_FILENAME}#{REGEX_LINK_RIGHT})\s*|\G)\s*(?:,\s*#{REGEX_LINK_LEFT}#{REGEX_FILENAME}#{REGEX_LINK_RIGHT})\s*/i
|
59
|
+
REGEX_TYPED_LINK_BLOCK_LIST_MKDN = /#{REGEX_LINK_TYPE_TXT}#{REGEX_LINK_TYPE}\n|\G(?:#{REGEX_LIST_ITEM}\n)/i
|
60
|
+
|
61
|
+
# parsing for wikilinks in html
|
62
|
+
# identify missing links in doc via .invalid-wiki-link class and nested doc-text.
|
63
|
+
REGEX_INVALID_WIKI_LINK = /invalid-wiki-link(?:[^\]]+)\[\[(?<wiki-text>([^\]]+))\]\]/i
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
data/lib/jekyll-wikilinks.rb
CHANGED
@@ -1,68 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "jekyll"
|
3
|
+
|
3
4
|
require_relative "jekyll-wikilinks/version"
|
4
5
|
|
5
|
-
#
|
6
|
-
# object -- which we need to build a element's href attribute.
|
7
|
-
class JekyllWikilinks < Jekyll::Generator
|
6
|
+
# in order of expected execution
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
# setup config
|
9
|
+
require_relative "jekyll-wikilinks/config"
|
10
|
+
Jekyll::Hooks.register :site, :after_init do |site|
|
11
|
+
# global '$wiki_conf' to ensure that all local jekyll plugins
|
12
|
+
# are reading from the same configuration
|
13
|
+
# (global var is not ideal, but is DRY)
|
14
|
+
$wiki_conf = Jekyll::WikiLinks::PluginConfig.new(site.config)
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
# setup docs (based on configs)
|
18
|
+
require_relative "jekyll-wikilinks/patch/doc_manager"
|
19
|
+
Jekyll::Hooks.register :site, :post_read do |site|
|
20
|
+
if !$wiki_conf.disabled?
|
21
|
+
site.doc_mngr = Jekyll::WikiLinks::DocManager.new(site)
|
22
|
+
end
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
# Convert all Wiki/Roam-style double-bracket link syntax to plain HTML
|
26
|
-
# anchor tag elements (<a>) with "internal-link" CSS class
|
27
|
-
all_notes.each do |note_potentially_linked_to|
|
28
|
-
namespace_from_filename = File.basename(
|
29
|
-
note_potentially_linked_to.basename,
|
30
|
-
File.extname(note_potentially_linked_to.basename)
|
31
|
-
)
|
25
|
+
# convert
|
26
|
+
require_relative "jekyll-wikilinks/plugins/converter"
|
32
27
|
|
33
|
-
|
34
|
-
|
35
|
-
# ⬜️ vscode-markdown-notes version: (\[\[)([^\|\]]+)(\]\])
|
36
|
-
note.content = note.content.gsub(
|
37
|
-
/\[\[#{namespace_from_filename}\]\]/i,
|
38
|
-
"<a class='wiki-link' href='#{site.baseurl}#{note_potentially_linked_to.data['permalink']}#{link_extension}'>#{note_potentially_linked_to.data['title'].downcase}</a>"
|
39
|
-
)
|
28
|
+
# generate
|
29
|
+
require_relative "jekyll-wikilinks/plugins/generator"
|
40
30
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
"<a class='wiki-link' href='#{site.baseurl}#{note_potentially_linked_to.data['permalink']}#{link_extension}'>\\4</a>"
|
47
|
-
)
|
31
|
+
# convert fores
|
32
|
+
# Jekyll::Hooks.register :documents, :pre_convert do |doc|
|
33
|
+
# Jekyll:WikiLinks::Parser.parse_blocks(doc)
|
34
|
+
# @site.link_index.populate_fores(doc, typed_link_blocks, md_docs)
|
35
|
+
# end
|
48
36
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
"<a class='wiki-link' href='#{site.baseurl}#{note_potentially_linked_to.data['permalink']}#{link_extension}'>\\2</a>"
|
55
|
-
)
|
56
|
-
end
|
37
|
+
# convert backs
|
38
|
+
# Jekyll::Hooks.register :documents, :pre_convert do |doc|
|
39
|
+
# Jekyll:WikiLinks::Parser.parse_inlines(doc)
|
40
|
+
# @site.link_index.populate_backs(doc, typed_link_blocks, md_docs)
|
41
|
+
# end
|
57
42
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
)
|
67
|
-
end
|
68
|
-
end
|
43
|
+
# generate metadata
|
44
|
+
# Jekyll::Hooks.register :documents, :post_convert do |doc|
|
45
|
+
# Jekyll:WikiLinks::Generator.generate(doc)
|
46
|
+
# end
|
47
|
+
|
48
|
+
# hook up liquid filters
|
49
|
+
require_relative "jekyll-wikilinks/plugins/filter"
|
50
|
+
Liquid::Template.register_filter(Jekyll::WikiLinks::TypeFilters)
|
metadata
CHANGED
@@ -1,30 +1,69 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-wikilinks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- manunamz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
12
|
-
dependencies:
|
11
|
+
date: 2021-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jekyll
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.12.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.12.3
|
13
41
|
description:
|
14
42
|
email:
|
15
|
-
-
|
43
|
+
- manunamz@pm.me
|
16
44
|
executables: []
|
17
45
|
extensions: []
|
18
46
|
extra_rdoc_files: []
|
19
47
|
files:
|
20
48
|
- lib/jekyll-wikilinks.rb
|
49
|
+
- lib/jekyll-wikilinks/config.rb
|
50
|
+
- lib/jekyll-wikilinks/patch/context.rb
|
51
|
+
- lib/jekyll-wikilinks/patch/doc_manager.rb
|
52
|
+
- lib/jekyll-wikilinks/patch/site.rb
|
53
|
+
- lib/jekyll-wikilinks/plugins/converter.rb
|
54
|
+
- lib/jekyll-wikilinks/plugins/filter.rb
|
55
|
+
- lib/jekyll-wikilinks/plugins/generator.rb
|
56
|
+
- lib/jekyll-wikilinks/util/link_index.rb
|
57
|
+
- lib/jekyll-wikilinks/util/parser.rb
|
58
|
+
- lib/jekyll-wikilinks/util/regex.rb
|
21
59
|
- lib/jekyll-wikilinks/version.rb
|
22
|
-
homepage: https://github.com/
|
60
|
+
homepage: https://github.com/manunamz/jekyll-wikilinks
|
23
61
|
licenses:
|
24
|
-
-
|
62
|
+
- GPL3
|
25
63
|
metadata:
|
26
|
-
homepage_uri: https://github.com/
|
27
|
-
source_code_uri: https://github.com/
|
64
|
+
homepage_uri: https://github.com/manunamz/jekyll-wikilinks
|
65
|
+
source_code_uri: https://github.com/manunamz/jekyll-wikilinks
|
66
|
+
changelog_uri: https://github.com/manunamz/jekyll-wikilinks/blob/main/CHANGELOG.md
|
28
67
|
post_install_message:
|
29
68
|
rdoc_options: []
|
30
69
|
require_paths:
|
@@ -43,5 +82,5 @@ requirements: []
|
|
43
82
|
rubygems_version: 3.2.17
|
44
83
|
signing_key:
|
45
84
|
specification_version: 4
|
46
|
-
summary: Add support for [[wikilinks]] (in markdown).
|
85
|
+
summary: Add jekyll support for [[wikilinks]] (in markdown files).
|
47
86
|
test_files: []
|